Nico Huber has submitted this change and it was merged. ( https://review.coreboot.org/23338 )
Change subject: digilent_spi: add a driver for the iCEblink40 development board ......................................................................
digilent_spi: add a driver for the iCEblink40 development board
This is driver that supports the Lattice iCE40 evaluation kits. On the board is a SPI flash memory chip labeled ST 25P10VP.
Tested to work read/write/erase with "-p digilent_spi -c M25P10" or with a patch that resets the part beforehands (in which case it gets detected as a M25P10-A and is way faster due to paged writes).
Change-Id: I7ffcd9a2db4395816f0e8b6ce6c3b0d8e930c9e6 Signed-off-by: Lubomir Rintel lkundrak@v3.sk Reviewed-on: https://review.coreboot.org/23338 Tested-by: build bot (Jenkins) no-reply@coreboot.org Reviewed-by: Nico Huber nico.h@gmx.de --- M Makefile A digilent_spi.c M flashrom.8.tmpl M flashrom.c M programmer.h 5 files changed, 508 insertions(+), 2 deletions(-)
Approvals: build bot (Jenkins): Verified Nico Huber: Looks good to me, approved
diff --git a/Makefile b/Makefile index 746d8d6..cd72972 100644 --- a/Makefile +++ b/Makefile @@ -640,6 +640,9 @@ # Winchiphead CH341A CONFIG_CH341A_SPI ?= yes
+# Digilent Development board JTAG +CONFIG_DIGILENT_SPI ?= yes + # Disable wiki printing by default. It is only useful if you have wiki access. CONFIG_PRINT_WIKI ?= no
@@ -667,6 +670,7 @@ ifeq ($(CONFIG_ENABLE_LIBUSB1_PROGRAMMERS), no) override CONFIG_CH341A_SPI = no override CONFIG_DEDIPROG = no +override CONFIG_DIGILENT_SPI = no endif ifeq ($(CONFIG_ENABLE_LIBPCI_PROGRAMMERS), no) override CONFIG_INTERNAL = no @@ -934,6 +938,12 @@ NEED_LIBUSB1 += CONFIG_CH341A_SPI endif
+ifeq ($(CONFIG_DIGILENT_SPI), yes) +FEATURE_CFLAGS += -D'CONFIG_DIGILENT_SPI=1' +PROGRAMMER_OBJS += digilent_spi.o +NEED_LIBUSB1 += CONFIG_DIGILENT_SPI +endif + ifneq ($(NEED_SERIAL), ) LIB_OBJS += serial.o custom_baud.o endif diff --git a/digilent_spi.c b/digilent_spi.c new file mode 100644 index 0000000..458e1b6 --- /dev/null +++ b/digilent_spi.c @@ -0,0 +1,453 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2018 Lubomir Rintel lkundrak@v3.sk + * + * Based on ft2232_spi.c: + * + * Copyright (C) 2011 asbokid ballymunboy@gmail.com + * Copyright (C) 2014 Pluto Yang yangyj.ee@gmail.com + * Copyright (C) 2015-2016 Stefan Tauner + * Copyright (C) 2015 Urja Rannikko urjaman@gmail.com + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * The reverse-engineered protocol description was obtained from the + * iceBurn project https://github.com/davidcarne/iceBurn by + * David Carne davidcarne@gmail.com. + */ + +#include <stdlib.h> +#include <string.h> +#include <libusb.h> +#include "programmer.h" + +/* This is pretty much arbitrarily chosen. After one second without a + * response we can be pretty sure we're not going to succeed. */ +#define USB_TIMEOUT 1000 + +#define CMD_WRITE_EP 0x01 +#define CMD_READ_EP 0x82 +#define DATA_WRITE_EP 0x03 +#define DATA_READ_EP 0x84 + +static struct libusb_device_handle *handle = NULL; +static bool reset_board; + +#define DIGILENT_VID 0x1443 +#define DIGILENT_JTAG_PID 0x0007 + +const struct dev_entry devs_digilent_spi[] = { + { DIGILENT_VID, DIGILENT_JTAG_PID, OK, "Digilent", "Development board JTAG" }, + { 0 }, +}; + +/* Control endpoint commands. */ +enum { + GET_BOARD_TYPE = 0xe2, + GET_BOARD_SERIAL = 0xe4, +}; + +/* Command bulk endpoint command groups. */ +enum { + CMD_GPIO = 0x03, + CMD_BOARD = 0x04, + CMD_SPI = 0x06, +}; + +/* GPIO subcommands. */ +enum { + CMD_GPIO_OPEN = 0x00, + CMD_GPIO_CLOSE = 0x01, + CMD_GPIO_SET_DIR = 0x04, + CMD_GPIO_SET_VAL = 0x06, +}; + +/* Board subcommands. */ +enum { + CMD_BOARD_OPEN = 0x00, + CMD_BOARD_CLOSE = 0x01, + CMD_BOARD_SET_REG = 0x04, + CMD_BOARD_GET_REG = 0x05, + CMD_BOARD_PL_STAT = 0x85, +}; + +/* SPI subcommands. */ +enum { + CMD_SPI_OPEN = 0x00, + CMD_SPI_CLOSE = 0x01, + CMD_SPI_SET_SPEED = 0x03, + CMD_SPI_SET_MODE = 0x05, + CMD_SPI_SET_CS = 0x06, + CMD_SPI_START_IO = 0x07, + CMD_SPI_TX_END = 0x87, +}; + +static int do_command(uint8_t *req, int req_len, uint8_t *res, int res_len) +{ + int tx_len = 0; + int ret; + + req[0] = req_len - 1; + ret = libusb_bulk_transfer(handle, CMD_WRITE_EP, req, req_len, &tx_len, USB_TIMEOUT); + if (ret) { + msg_perr("Failed to issue a command: '%s'\n", libusb_error_name(ret)); + return -1; + } + + if (tx_len != req_len) { + msg_perr("Short write issuing a command\n"); + return -1; + } + + ret = libusb_bulk_transfer(handle, CMD_READ_EP, res, res_len, &tx_len, USB_TIMEOUT); + if (ret) { + msg_perr("Failed to get a response: '%s'\n", libusb_error_name(ret)); + return -1; + } + + if (tx_len != res_len) { + msg_perr("Short read getting a response\n"); + return -1; + } + + if (res[0] != res_len -1) { + msg_perr("Response indicates incorrect length.\n"); + return -1; + } + + return 0; +} + +static int gpio_open(void) +{ + uint8_t req[] = { 0x00, CMD_GPIO, CMD_GPIO_OPEN, 0x00 }; + uint8_t res[2]; + + return do_command(req, sizeof(req), res, sizeof(res)); +} + +static int gpio_set_dir(uint8_t direction) +{ + uint8_t req[] = { 0x00, CMD_GPIO, CMD_GPIO_SET_DIR, 0x00, + direction, 0x00, 0x00, 0x00 }; + uint8_t res[6]; + + return do_command(req, sizeof(req), res, sizeof(res)); +} + +static int gpio_set_value(uint8_t value) +{ + uint8_t req[] = { 0x00, CMD_GPIO, CMD_GPIO_SET_VAL, 0x00, + value, 0x00, 0x00, 0x00 }; + uint8_t res[2]; + + return do_command(req, sizeof(req), res, sizeof(res)); +} + +static int spi_open(void) +{ + uint8_t req[] = { 0x00, CMD_SPI, CMD_SPI_OPEN, 0x00 }; + uint8_t res[2]; + + return do_command(req, sizeof(req), res, sizeof(res)); +} + +static int spi_set_speed(uint32_t speed) +{ + uint8_t req[] = { 0x00, CMD_SPI, CMD_SPI_SET_SPEED, 0x00, + (speed) & 0xff, + (speed >> 8) & 0xff, + (speed >> 16) & 0xff, + (speed >> 24) & 0xff }; + uint8_t res[6]; + uint32_t real_speed; + int ret; + + ret = do_command(req, sizeof(req), res, sizeof(res)); + if (ret) + return ret; + + real_speed = (res[5] << 24) | (res[4] << 16) | (res[3] << 8) | res[2]; + if (real_speed != speed) + msg_pwarn("SPI speed set to %d instead of %d\n", real_speed, speed); + + return 0; +} + +static int spi_set_mode(uint8_t mode) +{ + uint8_t req[] = { 0x00, CMD_SPI, CMD_SPI_SET_MODE, 0x00, mode }; + uint8_t res[2]; + + return do_command(req, sizeof(req), res, sizeof(res)); +} + +static int spi_set_cs(uint8_t cs) +{ + uint8_t req[] = { 0x00, CMD_SPI, CMD_SPI_SET_CS, 0x00, cs }; + uint8_t res[2]; + + return do_command(req, sizeof(req), res, sizeof(res)); +} + +static int spi_start_io(uint8_t read_follows, uint32_t write_len) +{ + uint8_t req[] = { 0x00, CMD_SPI, CMD_SPI_START_IO, 0x00, + 0x00, 0x00, /* meaning unknown */ + read_follows, + (write_len) & 0xff, + (write_len >> 8) & 0xff, + (write_len >> 16) & 0xff, + (write_len >> 24) & 0xff }; + uint8_t res[2]; + + return do_command(req, sizeof(req), res, sizeof(res)); +} + +static int spi_tx_end(uint8_t read_follows, uint32_t tx_len) +{ + uint8_t req[] = { 0x00, CMD_SPI, CMD_SPI_TX_END, 0x00 }; + uint8_t res[read_follows ? 10 : 6]; + int ret; + uint32_t count; + + ret = do_command(req, sizeof(req), res, sizeof(res)); + if (ret != 0) + return ret; + + if ((res[1] & 0x80) == 0) { + msg_perr("%s: response missing a write count\n", __func__); + return -1; + } + + count = res[2] | (res[3] << 8) | (res[4] << 16) | res[5] << 24; + if (count != tx_len) { + msg_perr("%s: wrote only %d bytes instead of %d\n", __func__, count, tx_len); + return -1; + } + + if (read_follows) { + if ((res[1] & 0x40) == 0) { + msg_perr("%s: response missing a read count\n", __func__); + return -1; + } + + count = res[6] | (res[7] << 8) | (res[8] << 16) | res[9] << 24; + if (count != tx_len) { + msg_perr("%s: read only %d bytes instead of %d\n", __func__, count, tx_len); + return -1; + } + } + + return 0; +} + +static int digilent_spi_send_command(struct flashctx *flash, unsigned int writecnt, unsigned int readcnt, + const unsigned char *writearr, unsigned char *readarr) +{ + int ret; + int len = writecnt + readcnt; + int tx_len = 0; + uint8_t buf[len]; + uint8_t read_follows = readcnt > 0 ? 1 : 0; + + memcpy(buf, writearr, writecnt); + memset(buf + writecnt, 0xff, readcnt); + + ret = spi_set_cs(0); + if (ret != 0) + return ret; + + ret = spi_start_io(read_follows, writecnt); + if (ret != 0) + return ret; + + ret = libusb_bulk_transfer(handle, DATA_WRITE_EP, buf, len, &tx_len, USB_TIMEOUT); + if (ret != 0) { + msg_perr("%s: failed to write data: '%s'\n", __func__, libusb_error_name(ret)); + return -1; + } + if (tx_len != len) { + msg_perr("%s: short write\n", __func__); + return -1; + } + + if (read_follows) { + ret = libusb_bulk_transfer(handle, DATA_READ_EP, buf, len, &tx_len, USB_TIMEOUT); + if (ret != 0) { + msg_perr("%s: failed to read data: '%s'\n", __func__, libusb_error_name(ret)); + return -1; + } + if (tx_len != len) { + msg_perr("%s: short read\n", __func__); + return -1; + } + } + + ret = spi_tx_end(read_follows, len); + if (ret != 0) + return ret; + + ret = spi_set_cs(1); + if (ret != 0) + return ret; + + memcpy(readarr, &buf[writecnt], readcnt); + + return 0; +} + +static const struct spi_master spi_master_digilent_spi = { + .type = SPI_CONTROLLER_DIGILENT_SPI, + .features = SPI_MASTER_4BA, + .max_data_read = 252, + .max_data_write = 252, + .command = digilent_spi_send_command, + .multicommand = default_spi_send_multicommand, + .read = default_spi_read, + .write_256 = default_spi_write_256, + .write_aai = default_spi_write_aai, +}; + + +static int digilent_spi_shutdown(void *data) +{ + if (reset_board) + gpio_set_dir(0); + + libusb_close(handle); + handle = NULL; + + return 0; +} + +static bool default_reset(void) +{ + char board[17]; + + libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR, + GET_BOARD_TYPE, 0, 0, + (unsigned char *)board, sizeof(board) - 1, USB_TIMEOUT); + board[sizeof(board) -1] = '\0'; + + if (strcmp(board, "iCE40") == 0) + return true; + + msg_pwarn("%s: unknown board '%s' not attempting a reset. " + "Override with '-p digilent_spi=reset=1'.\n", __func__, board); + return false; +} + +struct digilent_spispeeds { + const char *const name; + const int speed; +}; + +static const struct digilent_spispeeds spispeeds[] = { + { "4M", 4000000 }, + { "2M", 2000000 }, + { "1M", 1000000 }, + { "500k", 500000 }, + { "250k", 250000 }, + { "125k", 125000 }, + { "62.5k", 62500 }, + { NULL, 0 }, +}; + +int digilent_spi_init(void) +{ + char *p; + uint32_t speed_hz = spispeeds[0].speed; + int i; + + if (handle != NULL) { + msg_cerr("%s: handle already set! Please report a bug at flashrom@flashrom.org\n", __func__); + return -1; + } + + int32_t ret = libusb_init(NULL); + if (ret < 0) { + msg_perr("%s: couldn't initialize libusb!\n", __func__); + return -1; + } + + libusb_set_debug(NULL, 3); + + uint16_t vid = devs_digilent_spi[0].vendor_id; + uint16_t pid = devs_digilent_spi[0].device_id; + handle = libusb_open_device_with_vid_pid(NULL, vid, pid); + if (handle == NULL) { + msg_perr("%s: couldn't open device %04x:%04x.\n", __func__, vid, pid); + return -1; + } + + ret = libusb_claim_interface(handle, 0); + if (ret != 0) { + msg_perr("%s: failed to claim interface 0: '%s'\n", __func__, libusb_error_name(ret)); + goto close_handle; + } + + p = extract_programmer_param("spispeed"); + if (p) { + for (i = 0; spispeeds[i].name; ++i) { + if (!strcasecmp(spispeeds[i].name, p)) { + speed_hz = spispeeds[i].speed; + break; + } + } + if (!spispeeds[i].name) { + msg_perr("Error: Invalid spispeed value: '%s'.\n", p); + free(p); + goto close_handle; + } + free(p); + } + + p = extract_programmer_param("reset"); + if (p && strlen(p)) + reset_board = (p[0] == '1'); + else + reset_board = default_reset(); + free(p); + + if (reset_board) { + if (gpio_open() != 0) + goto close_handle; + if (gpio_set_dir(1) != 0) + goto close_handle; + if (gpio_set_value(0) != 0) + goto close_handle; + } + + if (spi_open() != 0) + goto close_handle; + if (spi_set_speed(speed_hz) != 0) + goto close_handle; + if (spi_set_mode(0x00) != 0) + goto close_handle; + + register_shutdown(digilent_spi_shutdown, NULL); + register_spi_master(&spi_master_digilent_spi); + + return 0; + +close_handle: + libusb_close(handle); + handle = NULL; + return -1; +} diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl index a3528f1..70af395 100644 --- a/flashrom.8.tmpl +++ b/flashrom.8.tmpl @@ -303,6 +303,8 @@ .sp .BR "* ch341a_spi" " (for SPI flash ROMs attached to WCH CH341A)" .sp +.BR "* digilent_spi" " (for SPI flash ROMs attached to iCEblink40 development boards)" +.sp Some programmers have optional or mandatory parameters which are described in detail in the .B PROGRAMMER-SPECIFIC INFORMATION @@ -1083,6 +1085,23 @@ .BR "ch341a_spi " programmer The WCH CH341A programmer does not support any parameters currently. SPI frequency is fixed at 2 MHz, and CS0 is used as per the device. +.SS +.BR "digilent_spi " programmer +.IP +An optional +.B spispeed +parameter specifies the frequency of the SPI bus. +Syntax is +.sp +.B " flashrom -p digilent_spi:spispeed=frequency" +.sp +where +.B frequency +can be +.BR 62.5k ", " 125k ", " 250k ", " 500k ", " 1M ", " 2M " or " 4M +(in Hz). The default is a frequency of 4 MHz. +.sp +.SS .SH EXAMPLES To back up and update your BIOS, run .sp @@ -1152,8 +1171,8 @@ .BR gfxnvidia ", " drkaiser ", " satasii ", " satamv ", " atahpt ", " atavia " and " atapromise have to be run as superuser/root, and need additional raw access permission. .sp -.BR serprog ", " buspirate_spi ", " dediprog ", " usbblaster_spi ", " ft2232_spi ", " pickit2_spi " and " \ -ch341a_spi +.BR serprog ", " buspirate_spi ", " dediprog ", " usbblaster_spi ", " ft2232_spi ", " pickit2_spi ", " \ +ch341a_spi " and " digilent_spi can be run as normal user on most operating systems if appropriate device permissions are set. .sp diff --git a/flashrom.c b/flashrom.c index f85dbb1..1866a18 100644 --- a/flashrom.c +++ b/flashrom.c @@ -413,6 +413,18 @@ }, #endif
+#if CONFIG_DIGILENT_SPI == 1 + { + .name = "digilent_spi", + .type = USB, + .devs.dev = devs_digilent_spi, + .init = digilent_spi_init, + .map_flash_region = fallback_map, + .unmap_flash_region = fallback_unmap, + .delay = internal_delay, + }, +#endif + {0}, /* This entry corresponds to PROGRAMMER_INVALID. */ };
diff --git a/programmer.h b/programmer.h index e49f262..ff81036 100644 --- a/programmer.h +++ b/programmer.h @@ -112,6 +112,9 @@ #if CONFIG_CH341A_SPI == 1 PROGRAMMER_CH341A_SPI, #endif +#if CONFIG_DIGILENT_SPI == 1 + PROGRAMMER_DIGILENT_SPI, +#endif PROGRAMMER_INVALID /* This must always be the last entry. */ };
@@ -549,6 +552,12 @@ extern const struct dev_entry devs_ch341a_spi[]; #endif
+/* digilent_spi.c */ +#if CONFIG_DIGILENT_SPI == 1 +int digilent_spi_init(void); +extern const struct dev_entry devs_digilent_spi[]; +#endif + /* flashrom.c */ struct decode_sizes { uint32_t parallel; @@ -614,6 +623,9 @@ #if CONFIG_CH341A_SPI == 1 SPI_CONTROLLER_CH341A_SPI, #endif +#if CONFIG_DIGILENT_SPI == 1 + SPI_CONTROLLER_DIGILENT_SPI, +#endif };
#define MAX_DATA_UNSPECIFIED 0