1
0
mirror of https://review.coreboot.org/flashrom.git synced 2025-04-28 07:23:43 +02:00

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>
This commit is contained in:
Lubomir Rintel 2018-01-14 17:35:33 +01:00 committed by Nico Huber
parent ac01baa073
commit b2154e8a1d
5 changed files with 508 additions and 2 deletions

View File

@ -640,6 +640,9 @@ CONFIG_IT8212 ?= yes
# Winchiphead CH341A # Winchiphead CH341A
CONFIG_CH341A_SPI ?= yes 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. # Disable wiki printing by default. It is only useful if you have wiki access.
CONFIG_PRINT_WIKI ?= no CONFIG_PRINT_WIKI ?= no
@ -667,6 +670,7 @@ endif
ifeq ($(CONFIG_ENABLE_LIBUSB1_PROGRAMMERS), no) ifeq ($(CONFIG_ENABLE_LIBUSB1_PROGRAMMERS), no)
override CONFIG_CH341A_SPI = no override CONFIG_CH341A_SPI = no
override CONFIG_DEDIPROG = no override CONFIG_DEDIPROG = no
override CONFIG_DIGILENT_SPI = no
endif endif
ifeq ($(CONFIG_ENABLE_LIBPCI_PROGRAMMERS), no) ifeq ($(CONFIG_ENABLE_LIBPCI_PROGRAMMERS), no)
override CONFIG_INTERNAL = no override CONFIG_INTERNAL = no
@ -934,6 +938,12 @@ PROGRAMMER_OBJS += ch341a_spi.o
NEED_LIBUSB1 += CONFIG_CH341A_SPI NEED_LIBUSB1 += CONFIG_CH341A_SPI
endif 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), ) ifneq ($(NEED_SERIAL), )
LIB_OBJS += serial.o custom_baud.o LIB_OBJS += serial.o custom_baud.o
endif endif

453
digilent_spi.c Normal file
View File

@ -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;
}

View File

@ -303,6 +303,8 @@ bitbanging adapter)
.sp .sp
.BR "* ch341a_spi" " (for SPI flash ROMs attached to WCH CH341A)" .BR "* ch341a_spi" " (for SPI flash ROMs attached to WCH CH341A)"
.sp .sp
.BR "* digilent_spi" " (for SPI flash ROMs attached to iCEblink40 development boards)"
.sp
Some programmers have optional or mandatory parameters which are described Some programmers have optional or mandatory parameters which are described
in detail in the in detail in the
.B PROGRAMMER-SPECIFIC INFORMATION .B PROGRAMMER-SPECIFIC INFORMATION
@ -1083,6 +1085,23 @@ Please also note that the mstarddc_spi driver only works on Linux.
.BR "ch341a_spi " programmer .BR "ch341a_spi " programmer
The WCH CH341A programmer does not support any parameters currently. SPI frequency is fixed at 2 MHz, and CS0 is 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. 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 .SH EXAMPLES
To back up and update your BIOS, run To back up and update your BIOS, run
.sp .sp
@ -1152,8 +1171,8 @@ needs no access permissions at all.
.BR gfxnvidia ", " drkaiser ", " satasii ", " satamv ", " atahpt ", " atavia " and " atapromise .BR gfxnvidia ", " drkaiser ", " satasii ", " satamv ", " atahpt ", " atavia " and " atapromise
have to be run as superuser/root, and need additional raw access permission. have to be run as superuser/root, and need additional raw access permission.
.sp .sp
.BR serprog ", " buspirate_spi ", " dediprog ", " usbblaster_spi ", " ft2232_spi ", " pickit2_spi " and " \ .BR serprog ", " buspirate_spi ", " dediprog ", " usbblaster_spi ", " ft2232_spi ", " pickit2_spi ", " \
ch341a_spi ch341a_spi " and " digilent_spi
can be run as normal user on most operating systems if appropriate device can be run as normal user on most operating systems if appropriate device
permissions are set. permissions are set.
.sp .sp

View File

@ -413,6 +413,18 @@ const struct programmer_entry programmer_table[] = {
}, },
#endif #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. */ {0}, /* This entry corresponds to PROGRAMMER_INVALID. */
}; };

View File

@ -111,6 +111,9 @@ enum programmer {
#endif #endif
#if CONFIG_CH341A_SPI == 1 #if CONFIG_CH341A_SPI == 1
PROGRAMMER_CH341A_SPI, PROGRAMMER_CH341A_SPI,
#endif
#if CONFIG_DIGILENT_SPI == 1
PROGRAMMER_DIGILENT_SPI,
#endif #endif
PROGRAMMER_INVALID /* This must always be the last entry. */ PROGRAMMER_INVALID /* This must always be the last entry. */
}; };
@ -549,6 +552,12 @@ void ch341a_spi_delay(unsigned int usecs);
extern const struct dev_entry devs_ch341a_spi[]; extern const struct dev_entry devs_ch341a_spi[];
#endif #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 */ /* flashrom.c */
struct decode_sizes { struct decode_sizes {
uint32_t parallel; uint32_t parallel;
@ -614,6 +623,9 @@ enum spi_controller {
#if CONFIG_CH341A_SPI == 1 #if CONFIG_CH341A_SPI == 1
SPI_CONTROLLER_CH341A_SPI, SPI_CONTROLLER_CH341A_SPI,
#endif #endif
#if CONFIG_DIGILENT_SPI == 1
SPI_CONTROLLER_DIGILENT_SPI,
#endif
}; };
#define MAX_DATA_UNSPECIFIED 0 #define MAX_DATA_UNSPECIFIED 0