Lubomir Rintel has uploaded this change for review. ( 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 --- M Makefile A digilent_spi.c M flashrom.c M programmer.h 4 files changed, 428 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/38/23338/1
diff --git a/Makefile b/Makefile index 3370247..3df7387 100644 --- a/Makefile +++ b/Makefile @@ -637,6 +637,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
@@ -925,6 +928,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..61dc7f2 --- /dev/null +++ b/digilent_spi.c @@ -0,0 +1,395 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2018 Lubomir Rintel lkundrak@v3.sk + * + * Based on ch341a_spi.c and linux_spi.c: + * + * Copyright (C) 2009 Paul Fox pgf@laptop.org + * Copyright (C) 2009, 2010 Carl-Daniel Hailfinger + * Copyright (C) 2011 Sven Schnelle svens@stackframe.org + * + * 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. + * + * 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. + */ + +#if CONFIG_DIGILENT_SPI == 1 + +#include <stdlib.h> +#include <alloca.h> +#include <string.h> +#include <libusb.h> +#include "programmer.h" + +#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 = 0x0e2, + 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, 0); + 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, 0); + if (ret) { + msg_perr("Failed to get a response: '%s'\n", libusb_error_name(ret)); + return -1; + } + + return res_len; +} + +static int gpio_open(void) +{ + uint8_t req[] = { 0x00, CMD_GPIO, CMD_GPIO_OPEN, 0x00 }; + uint8_t res[2]; + + if (do_command(req, sizeof(req), res, sizeof(res)) != sizeof(res)) + return -1; + + return 0; +} + +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]; + + if (do_command(req, sizeof(req), res, sizeof(res)) != sizeof(res)) + return -1; + + return 0; +} + +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]; + + if (do_command(req, sizeof(req), res, sizeof(res)) != sizeof(res)) + return -1; + + return 0; +} + +static int spi_open(void) +{ + uint8_t req[] = { 0x00, CMD_SPI, CMD_SPI_OPEN, 0x00 }; + uint8_t res[2]; + + if (do_command(req, sizeof(req), res, sizeof(res)) != sizeof(res)) + return -1; + + return 0; +} + +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 != sizeof(res)) + return -1; + + 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]; + + if (do_command(req, sizeof(req), res, sizeof(res)) != sizeof(res)) + return -1; + + return 0; +} + +static int spi_set_cs(uint8_t cs) +{ + uint8_t req[] = { 0x00, CMD_SPI, CMD_SPI_SET_CS, 0x00, cs }; + uint8_t res[2]; + + if (do_command(req, sizeof(req), res, sizeof(res)) != sizeof(res)) + return -1; + + return 0; +} + +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]; + + if (do_command(req, sizeof(req), res, sizeof(res)) != sizeof(res)) + return -1; + + return 0; +} + +static int spi_tx_end(void) +{ + uint8_t req[] = { 0x00, CMD_SPI, CMD_SPI_TX_END, 0x00 }; + uint8_t res[10]; + + if (do_command(req, sizeof(req), res, sizeof(res)) < 6) + 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; + uint8_t read_follows = readcnt > 0 ? 1 : 0; + + buf = alloca(len); + if (buf == NULL) + return -1; + 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, 0); + if (ret != 0) + return ret; + + if (read_follows) { + ret = libusb_bulk_transfer(handle, DATA_READ_EP, buf, len, &tx_len, 0); + if (ret != 0) + return ret; + } + + ret = spi_tx_end(); + if (ret != 0) + return ret; + + ret = spi_set_cs(1); + if (ret != 0) + return ret; + + memcpy(readarr, &buf[writecnt], readcnt); + + return ret; +} + +static const struct spi_master spi_master_digilent_spi = { + .type = SPI_CONTROLLER_DIGILENT_SPI, + .features = SPI_MASTER_4BA, + .max_data_read = 64, + .max_data_write = 64, + .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); + + 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, 0); + 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; +} + +int digilent_spi_init(void) +{ + char *p, *endp; + uint32_t speed_hz = 4000000; + + 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: couldnt 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("spiclock"); + if (p && strlen(p)) { + speed_hz =(uint32_t)strtoul(p, &endp, 10) * 1000; + if (p == endp) { + msg_perr("%s: invalid clock: %s kHz\n", __func__, p); + free(p); + return -1; + } + } + 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) { + gpio_open(); + gpio_set_dir(1); + gpio_set_value(0); + } + + spi_open(); + spi_set_speed(speed_hz); + spi_set_mode(0x00); + + register_shutdown(digilent_spi_shutdown, NULL); + register_spi_master(&spi_master_digilent_spi); + + return 0; + +close_handle: + libusb_close(handle); + handle = NULL; + return -1; +} + +#endif diff --git a/flashrom.c b/flashrom.c index ac987fd..1496d4f 100644 --- a/flashrom.c +++ b/flashrom.c @@ -405,6 +405,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 8736449..646b2c8 100644 --- a/programmer.h +++ b/programmer.h @@ -113,6 +113,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. */ };
@@ -545,6 +548,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; @@ -607,6 +616,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