latticeSSPI: ECP5 driver for Slave SPI mode

This commit is contained in:
Gwenhael Goavec-Merou 2025-06-19 08:34:29 +02:00
parent 21d4fccf28
commit dede406ebc
5 changed files with 451 additions and 25 deletions

View File

@ -100,7 +100,6 @@ set(OPENFPGALOADER_SOURCE
src/efinix.cpp src/efinix.cpp
src/efinixHexParser.cpp src/efinixHexParser.cpp
src/fx2_ll.cpp src/fx2_ll.cpp
src/ice40.cpp
src/ihexParser.cpp src/ihexParser.cpp
src/pofParser.cpp src/pofParser.cpp
src/rawParser.cpp src/rawParser.cpp
@ -109,8 +108,6 @@ set(OPENFPGALOADER_SOURCE
src/usbBlaster.cpp src/usbBlaster.cpp
src/epcq.cpp src/epcq.cpp
src/svf_jtag.cpp src/svf_jtag.cpp
src/jedParser.cpp
src/feaparser.cpp
src/display.cpp src/display.cpp
src/jtag.cpp src/jtag.cpp
src/ftdiJtagBitbang.cpp src/ftdiJtagBitbang.cpp
@ -118,12 +115,10 @@ set(OPENFPGALOADER_SOURCE
src/configBitstreamParser.cpp src/configBitstreamParser.cpp
src/ftdipp_mpsse.cpp src/ftdipp_mpsse.cpp
src/main.cpp src/main.cpp
src/latticeBitParser.cpp
src/libusb_ll.cpp src/libusb_ll.cpp
src/gowin.cpp src/gowin.cpp
src/device.cpp src/device.cpp
src/jlink.cpp src/jlink.cpp
src/lattice.cpp
src/progressBar.cpp src/progressBar.cpp
src/fsparser.cpp src/fsparser.cpp
src/mcsParser.cpp src/mcsParser.cpp
@ -152,7 +147,6 @@ set(OPENFPGALOADER_HEADERS
src/efinix.hpp src/efinix.hpp
src/efinixHexParser.hpp src/efinixHexParser.hpp
src/fx2_ll.hpp src/fx2_ll.hpp
src/ice40.hpp
src/ihexParser.hpp src/ihexParser.hpp
src/pofParser.hpp src/pofParser.hpp
src/progressBar.hpp src/progressBar.hpp
@ -168,8 +162,6 @@ set(OPENFPGALOADER_HEADERS
src/fsparser.hpp src/fsparser.hpp
src/part.hpp src/part.hpp
src/board.hpp src/board.hpp
src/jedParser.hpp
src/feaparser.hpp
src/display.hpp src/display.hpp
src/mcsParser.hpp src/mcsParser.hpp
src/ftdipp_mpsse.hpp src/ftdipp_mpsse.hpp
@ -183,8 +175,6 @@ set(OPENFPGALOADER_HEADERS
src/gowin.hpp src/gowin.hpp
src/cable.hpp src/cable.hpp
src/ftdispi.hpp src/ftdispi.hpp
src/lattice.hpp
src/latticeBitParser.hpp
src/xilinx.hpp src/xilinx.hpp
src/xilinxMapParser.hpp src/xilinxMapParser.hpp
src/colognechip.hpp src/colognechip.hpp
@ -192,6 +182,25 @@ set(OPENFPGALOADER_HEADERS
src/esp_usb_jtag.hpp src/esp_usb_jtag.hpp
) )
# Lattice Drivers / Files parsers.
list(APPEND OPENFPGALOADER_SOURCE
src/ice40.cpp
src/lattice.cpp
src/latticeSSPI.cpp
src/feaparser.cpp
src/jedParser.cpp
src/latticeBitParser.cpp
)
list(APPEND OPENFPGALOADER_HEADERS
src/ice40.hpp
src/lattice.hpp
src/latticeSSPI.hpp
src/jedParser.hpp
src/feaparser.hpp
src/latticeBitParser.hpp
)
link_directories( link_directories(
${LIBUSB_LIBRARY_DIRS} ${LIBUSB_LIBRARY_DIRS}
${LIBFTDI_LIBRARY_DIRS} ${LIBFTDI_LIBRARY_DIRS}

View File

@ -95,8 +95,8 @@ typedef struct {
#define JTAG_BITBANG_BOARD(_name, _fpga_part, _cable, _rst, _done, _tms, _tck, _tdi, _tdo, _freq) \ #define JTAG_BITBANG_BOARD(_name, _fpga_part, _cable, _rst, _done, _tms, _tck, _tdi, _tdo, _freq) \
{_name, {"", _cable, _fpga_part, _rst, _done, 0, COMM_JTAG, { _tms, _tck, _tdi, _tdo }, {}, \ {_name, {"", _cable, _fpga_part, _rst, _done, 0, COMM_JTAG, { _tms, _tck, _tdi, _tdo }, {}, \
_freq, 0, 0, -1}} _freq, 0, 0, -1}}
#define SPI_BOARD(_name, _manufacturer, _cable, _rst, _done, _oe, _cs, _sck, _si, _so, _holdn, _wpn, _freq) \ #define SPI_BOARD(_name, _manufacturer, _fpga_part, _cable, _rst, _done, _oe, _cs, _sck, _si, _so, _holdn, _wpn, _freq) \
{_name, {_manufacturer, _cable, "", _rst, _done, _oe, COMM_SPI, {}, \ {_name, {_manufacturer, _cable, _fpga_part, _rst, _done, _oe, COMM_SPI, {}, \
{_cs, _sck, _so, _si, _holdn, _wpn}, _freq, 0, 0, -1}} {_cs, _sck, _so, _si, _holdn, _wpn}, _freq, 0, 0, -1}}
#define DFU_BOARD(_name, _fpga_part, _cable, _vid, _pid, _alt) \ #define DFU_BOARD(_name, _fpga_part, _cable, _vid, _pid, _alt) \
{_name, {"", _cable, _fpga_part, 0, 0, 0, COMM_DFU, {}, {}, 0, _vid, _pid, _alt}} {_name, {"", _cable, _fpga_part, 0, 0, 0, COMM_DFU, {}, {}, 0, _vid, _pid, _alt}}
@ -151,30 +151,34 @@ static std::map <std::string, target_board_t> board_list = {
JTAG_BOARD("deca", "10M50DA", "usb-blasterII",0, 0, CABLE_DEFAULT), JTAG_BOARD("deca", "10M50DA", "usb-blasterII",0, 0, CABLE_DEFAULT),
JTAG_BOARD("dragonL", "xc6slx25tcsg324", "", 0, 0, CABLE_DEFAULT), JTAG_BOARD("dragonL", "xc6slx25tcsg324", "", 0, 0, CABLE_DEFAULT),
JTAG_BOARD("ecp5_evn", "", "ft2232", 0, 0, CABLE_DEFAULT), JTAG_BOARD("ecp5_evn", "", "ft2232", 0, 0, CABLE_DEFAULT),
SPI_BOARD("ecp5_generic", "lattice", "ecp5", "",
0, 0, 0,
DBUS3, DBUS0, DBUS1, DBUS2,
0, 0, CABLE_DEFAULT),
JTAG_BOARD("ecpix5", "", "ecpix5-debug", 0, 0, CABLE_DEFAULT), JTAG_BOARD("ecpix5", "", "ecpix5-debug", 0, 0, CABLE_DEFAULT),
JTAG_BOARD("ecpix5_r03", "", "ft4232", 0, 0, CABLE_DEFAULT), JTAG_BOARD("ecpix5_r03", "", "ft4232", 0, 0, CABLE_DEFAULT),
SPI_BOARD("fireant", "efinix", "ft232", SPI_BOARD("fireant", "efinix", "trion", "ft232",
DBUS4, DBUS5, 0, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT), DBUS4, DBUS5, 0, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT),
DFU_BOARD("fomu", "", "dfu", 0x1209, 0x5bf0, 0), DFU_BOARD("fomu", "", "dfu", 0x1209, 0x5bf0, 0),
SPI_BOARD("ft2232_spi", "none", "ft2232", SPI_BOARD("ft2232_spi", "none", "none", "ft2232",
DBUS7, DBUS6, 0, DBUS7, DBUS6, 0,
DBUS4, DBUS0, DBUS1, DBUS2, DBUS4, DBUS0, DBUS1, DBUS2,
0, 0, CABLE_DEFAULT), 0, 0, CABLE_DEFAULT),
JTAG_BOARD("gcm_jtag", "none", "ft4232", 0, 0, CABLE_DEFAULT), JTAG_BOARD("gcm_jtag", "none", "ft4232", 0, 0, CABLE_DEFAULT),
SPI_BOARD("gcm_bootflash", "none", "ft4232_b", SPI_BOARD("gcm_bootflash", "none", "none", "ft4232_b",
0, 0, 0, 0, 0, 0,
DBUS3, DBUS0, DBUS1, DBUS2, DBUS3, DBUS0, DBUS1, DBUS2,
0, 0, CABLE_DEFAULT), 0, 0, CABLE_DEFAULT),
SPI_BOARD("gatemate_pgm_spi", "colognechip", "gatemate_pgm", SPI_BOARD("gatemate_pgm_spi", "colognechip", "GM1Ax", "gatemate_pgm",
DBUS4, DBUS5, CBUS0, DBUS3, DBUS0, DBUS1, DBUS2, 0, 0, CABLE_DEFAULT), DBUS4, DBUS5, CBUS0, DBUS3, DBUS0, DBUS1, DBUS2, 0, 0, CABLE_DEFAULT),
JTAG_BOARD("gatemate_evb_jtag", "", "gatemate_evb_jtag", 0, 0, CABLE_DEFAULT), JTAG_BOARD("gatemate_evb_jtag", "", "gatemate_evb_jtag", 0, 0, CABLE_DEFAULT),
SPI_BOARD("gatemate_evb_spi", "colognechip", "gatemate_evb_spi", SPI_BOARD("gatemate_evb_spi", "colognechip", "GM1Ax", "gatemate_evb_spi",
DBUS4, DBUS5, CBUS0, DBUS3, DBUS0, DBUS1, DBUS2, 0, 0, CABLE_DEFAULT), DBUS4, DBUS5, CBUS0, DBUS3, DBUS0, DBUS1, DBUS2, 0, 0, CABLE_DEFAULT),
JTAG_BOARD("genesys2", "xc7k325tffg900", "digilent_b", 0, 0, CABLE_DEFAULT), JTAG_BOARD("genesys2", "xc7k325tffg900", "digilent_b", 0, 0, CABLE_DEFAULT),
JTAG_BOARD("gr740-mini", "", "ft4232hp_b", 0, 0, CABLE_MHZ(1)), JTAG_BOARD("gr740-mini", "", "ft4232hp_b", 0, 0, CABLE_MHZ(1)),
JTAG_BOARD("hseda-xc6slx16", "xc6slx16ftg256", "", 0, 0, CABLE_DEFAULT), JTAG_BOARD("hseda-xc6slx16", "xc6slx16ftg256", "", 0, 0, CABLE_DEFAULT),
/* most ice40 boards uses the same pinout */ /* most ice40 boards uses the same pinout */
SPI_BOARD("ice40_generic", "lattice", "ft2232", SPI_BOARD("ice40_generic", "lattice", "ice40", "ft2232",
DBUS7, DBUS6, 0, DBUS7, DBUS6, 0,
DBUS4, DBUS0, DBUS1, DBUS2, DBUS4, DBUS0, DBUS1, DBUS2,
0, 0, CABLE_DEFAULT), 0, 0, CABLE_DEFAULT),
@ -234,13 +238,13 @@ static std::map <std::string, target_board_t> board_list = {
JTAG_BOARD("te0712_8", "xc7a200tfbg484", "", 0, 0, CABLE_MHZ(15)), JTAG_BOARD("te0712_8", "xc7a200tfbg484", "", 0, 0, CABLE_MHZ(15)),
JTAG_BOARD("tec0117", "", "ft2232", 0, 0, CABLE_DEFAULT), JTAG_BOARD("tec0117", "", "ft2232", 0, 0, CABLE_DEFAULT),
JTAG_BOARD("tec0330", "xc7vx330tffg1157", "", 0, 0, CABLE_MHZ(15)), JTAG_BOARD("tec0330", "xc7vx330tffg1157", "", 0, 0, CABLE_MHZ(15)),
SPI_BOARD("titanium_ti60_f225","efinix", "efinix_spi_ft4232", SPI_BOARD("titanium_ti60_f225","efinix", "titanium", "efinix_spi_ft4232",
DBUS4, DBUS5, DBUS7, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT), DBUS4, DBUS5, DBUS7, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT),
JTAG_BOARD("titanium_ti60_f225_jtag", "ti60f225","efinix_jtag_ft4232", 0, 0, CABLE_DEFAULT), JTAG_BOARD("titanium_ti60_f225_jtag", "ti60f225","efinix_jtag_ft4232", 0, 0, CABLE_DEFAULT),
SPI_BOARD("trion_t20_bga256", "efinix", "efinix_spi_ft2232", SPI_BOARD("trion_t20_bga256", "efinix", "trion", "efinix_spi_ft2232",
DBUS4, DBUS5, 0, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT), DBUS4, DBUS5, 0, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT),
JTAG_BOARD("trion_t20_bga256_jtag", "t20f256", "efinix_jtag_ft2232", 0, 0, CABLE_DEFAULT), JTAG_BOARD("trion_t20_bga256_jtag", "t20f256", "efinix_jtag_ft2232", 0, 0, CABLE_DEFAULT),
SPI_BOARD("trion_t120_bga576","efinix", "efinix_spi_ft2232", SPI_BOARD("trion_t120_bga576", "efinix", "trion", "efinix_spi_ft2232",
DBUS4, DBUS5, DBUS7, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT), DBUS4, DBUS5, DBUS7, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT),
JTAG_BOARD("trion_t120_bga576_jtag", "", "ft2232_b", 0, 0, CABLE_DEFAULT), JTAG_BOARD("trion_t120_bga576_jtag", "", "ft2232_b", 0, 0, CABLE_DEFAULT),
JTAG_BITBANG_BOARD("ulx2s", "", "ft232RL", 0, 0, JTAG_BITBANG_BOARD("ulx2s", "", "ft232RL", 0, 0,
@ -259,7 +263,7 @@ static std::map <std::string, target_board_t> board_list = {
JTAG_BOARD("vcu1525", "xcvu9p-fsgd2104", "ft4232", 0, 0, CABLE_MHZ(15)), JTAG_BOARD("vcu1525", "xcvu9p-fsgd2104", "ft4232", 0, 0, CABLE_MHZ(15)),
JTAG_BOARD("xem8320", "xcau25p-2ffvb676", "" , 0, 0, CABLE_DEFAULT), JTAG_BOARD("xem8320", "xcau25p-2ffvb676", "" , 0, 0, CABLE_DEFAULT),
JTAG_BOARD("xyloni_jtag", "t8f81", "efinix_jtag_ft4232", 0, 0, CABLE_DEFAULT), JTAG_BOARD("xyloni_jtag", "t8f81", "efinix_jtag_ft4232", 0, 0, CABLE_DEFAULT),
SPI_BOARD("xyloni_spi", "efinix", "efinix_spi_ft4232", SPI_BOARD("xyloni_spi", "efinix", "trion", "efinix_spi_ft4232",
DBUS4, DBUS5, DBUS7, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT), DBUS4, DBUS5, DBUS7, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT),
JTAG_BOARD("xtrx", "xc7a50tcpg236", "" , 0, 0, CABLE_DEFAULT), JTAG_BOARD("xtrx", "xc7a50tcpg236", "" , 0, 0, CABLE_DEFAULT),
JTAG_BOARD("zc702", "xc7z020clg484", "digilent", 0, 0, CABLE_DEFAULT), JTAG_BOARD("zc702", "xc7z020clg484", "digilent", 0, 0, CABLE_DEFAULT),

359
src/latticeSSPI.cpp Normal file
View File

@ -0,0 +1,359 @@
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright (C) 2025 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
*/
#include "latticeSSPI.hpp"
#include <string.h>
#include <unistd.h>
#define __STDC_FORMAT_MACROS
#include <cinttypes>
#include <iostream>
#include <string>
#include "device.hpp"
#include "ftdispi.hpp"
#include "latticeBitParser.hpp"
#include "progressBar.hpp"
#define SSPI_READ_ID 0xE0
#define SSPI_ISC_READ_STATUS 0x3C
#define ISC_ENABLE 0xC6 /* ISC_ENABLE - Offline Mode */
#define ISC_DISABLE 0x26 /* ISC_DISABLE */
#define READ_BUSY_FLAG 0xF0
#define FLASH_ERASE 0x0E
# define REG_STATUS_DONE (1 << 8) /* Flash or SRAM Done Flag (ISC_EN=0 -> 1 Successful Flash to SRAM transfer, ISC_EN=1 -> 1 Programmed) */
# define REG_STATUS_ISC_EN (1 << 9) /* Enable Configuration Interface (1=Enable, 0=Disable) */
# define REG_STATUS_BUSY (1 << 12) /* Busy Flag (1 = Busy) */
# define REG_STATUS_FAIL (1 << 13) /* Fail Flag (1 = Operation failed) */
# define REG_STATUS_PP_CFG (1 << 15) /* Password Protection All Enabled for CFG0 and CFG1 flash sectors 0=Disabled (Default), 1=Enabled */
# define REG_STATUS_PP_FSK (1 << 16) /* Password Protection Enabled for Feature and Security Key flash sectors 0=Disabled (Default), 1=Enabled */
# define REG_STATUS_PP_UFM (1 << 17) /* Password Protection enabled for all UFM flash sectors 0=Disabled (Default), 1=Enabled */
# define REG_STATUS_AUTH_DONE (1 << 18) /* Authentication done */
# define REG_STATUS_PRI_BOOT_FAIL (1 << 21) /* Primary boot failure (1= Fail) even though secondary boot successful */
# define REG_STATUS_CNF_CHK_MASK (0x0f << 23) /* Configuration Status Check */
# define REG_STATUS_EXEC_ERR (1 << 26) /*** NOT specified for MachXO3D ***/
# define REG_STATUS_DEV_VERIFIED (1 << 27) /* I=0 Device verified correct, I=1 Device failed to verify */
LatticeSSPI::LatticeSSPI(FtdiSpi *spi, const std::string &filename,
const std::string &file_type,
const int8_t verbose):
Device(NULL, filename, file_type, false, verbose), _spi(spi)
{
_spi->setMode(0);
}
uint32_t LatticeSSPI::char_array_to_word(const uint8_t *in)
{
return static_cast<uint32_t>(in[0] << 24) |
static_cast<uint32_t>(in[1] << 16) |
static_cast<uint32_t>(in[2] << 8) |
static_cast<uint32_t>(in[3] << 0);
}
bool LatticeSSPI::EnableISC(uint8_t /*flash_mode*/)
{
cmd_class_c(ISC_ENABLE);
if (!pollBusyFlag())
return false;
if (!checkStatus(REG_STATUS_ISC_EN, REG_STATUS_ISC_EN))
return false;
return true;
}
bool LatticeSSPI::DisableISC()
{
cmd_class_c(ISC_DISABLE);
if (!pollBusyFlag())
return false;
if (!checkStatus(0, REG_STATUS_ISC_EN))
return false;
return true;
}
uint32_t LatticeSSPI::readStatusReg()
{
uint8_t rx[4];
cmd_class_a(SSPI_ISC_READ_STATUS, rx, 4 * 8);
return char_array_to_word(rx);
}
/*
* Read Only command.
* 8bits command + 24 dummy bits + len rx
* len: nb bits
*/
bool LatticeSSPI::cmd_class_a(uint8_t cmd, uint8_t *rx, uint32_t len)
{
const uint32_t xferByteLen = (len + 7) / 8;
const uint32_t kBytetLen = xferByteLen + 3; // Convert bits to bytes after adding dummy
uint8_t lrx[kBytetLen];
uint8_t ltx[kBytetLen];
memset(ltx, 0x00, kBytetLen);
_spi->spi_put(cmd, ltx, lrx, kBytetLen);
memcpy(rx, &lrx[3], xferByteLen);
return true;
}
bool LatticeSSPI::cmd_class_c(uint8_t cmd)
{
const uint8_t ltx[3] = {0x00, 0x00, 0x00};
_spi->spi_put(cmd, ltx, NULL, 3);
return true;
}
uint32_t LatticeSSPI::idCode()
{
uint8_t rx[4];
cmd_class_a(SSPI_READ_ID, rx, 32);
return char_array_to_word(rx);
}
bool LatticeSSPI::pollBusyFlag(bool verbose)
{
uint8_t rx;
int timeout = 0;
do {
cmd_class_a(READ_BUSY_FLAG, &rx, 8);
if (verbose)
printf("pollBusyFlag :%02x\n", rx);
if (timeout == 100000000){
std::cerr << "timeout" << std::endl;
return false;
} else {
timeout++;
}
} while (rx != 0);
return true;
}
bool LatticeSSPI::flashErase(uint32_t /*mask*/)
{
const uint8_t tx[] = {0x01, 0x00, 0x00};
_spi->spi_put(FLASH_ERASE, tx, 0, 3);
if (!pollBusyFlag())
return false;
if (!checkStatus(0, REG_STATUS_FAIL))
return false;
return true;
}
bool LatticeSSPI::program_mem()
{
LatticeBitParser _bit(_filename, false, _verbose);
printInfo("Open file: ", false);
printSuccess("DONE");
const bool err = _bit.parse();
printInfo("Parse file: ", false);
if (err == EXIT_FAILURE) {
printError("FAIL");
return false;
} else {
printSuccess("DONE");
}
if (_verbose)
_bit.displayHeader();
/* Prepare bitstream */
const uint8_t *data = _bit.getData();
const int length = _bit.getLength() / 8;
/* read ID Code 0xE0 and compare to bitstream */
const uint32_t bit_idcode = std::stoul(_bit.getHeaderVal("idcode").c_str(), NULL, 16);
const uint32_t idcode = idCode();
if (idcode != bit_idcode) {
char mess[256];
snprintf(mess, 256, "mismatch between target's idcode and bitstream idcode\n"
"\tbitstream has 0x%08X hardware requires 0x%08x", bit_idcode, idcode);
printError(mess);
return false;
}
if (_verbose) {
printf("IDCode : 0x%08x\n", idcode);
displayReadReg(readStatusReg());
}
/* LSC_REFRESH 0x79 -- "Equivalent to toggle PROGRAMN pin"
* We REFRESH only if the fpga is in a status of error due to
* the previous bitstream. For example, this happens if
* no bitstream is present on the SPI FLASH
*/
cmd_class_c(0x79);
usleep(8000);
/* ISC Enable 0xC6 */
printInfo("Enable configuration: ", false);
if (!EnableISC(0x00)) {
printError("FAIL");
displayReadReg(readStatusReg());
return false;
} else {
printSuccess("DONE");
}
displayReadReg(readStatusReg());
/* ISC ERASE
* For Nexus family (from svf file): 1 byte to tx 0x00
*/
printInfo("SRAM erase: ", false);
if (flashErase(0x01/*mask_erase[0]*/) == false) {
printError("FAIL");
displayReadReg(readStatusReg());
return false;
} else {
printSuccess("DONE");
}
/* LSC_INIT_ADDRESS */
cmd_class_c(0x46);
/* Switch to manual CS.
* This signal must be low for LSC_BITSTREAM_BURST and
* all bitstream TX.
*/
_spi->setCSmode(FtdiSpi::SPI_CS_MANUAL);
_spi->clearCs();
cmd_class_c(0x7A);
int size = 1024;
ProgressBar progress("Loading", length, 50, _quiet);
for (int i = 0; i < length; i += size) {
progress.display(i);
if (length < i + size)
size = length-i;
_spi->spi_put(&data[i], NULL, size);
}
progress.done();
/* Switch CS to Automatic mode */
_spi->setCs();
_spi->setCSmode(FtdiSpi::SPI_CS_AUTO);
usleep(1000);
/* disable configuration mode */
printInfo("Disable configuration: ", false);
if (!DisableISC()) {
printError("FAIL");
displayReadReg(readStatusReg());
return false;
} else {
printSuccess("DONE");
}
if (_verbose)
displayReadReg(readStatusReg());
const uint8_t nop[] = {0xff, 0xff, 0xff, 0xff};
_spi->spi_put(nop, NULL, 4);
return true;
}
void LatticeSSPI::program(unsigned int /*offset*/, bool /*unprotect_flash*/)
{
if (_mode == FLASH_MODE)
throw std::runtime_error("Flash mode not avaible when programming in Slave SPI");
const bool retval = program_mem();
if (!retval)
throw std::exception();
}
void LatticeSSPI::displayReadReg(uint32_t dev)
{
printf("displayReadReg 0x%08x\n", dev);
if (dev & 1<<0) printf("\tTRAN Mode\n");
printf("\tConfig Target Selection : %" PRIx32 "\n", (dev >> 1) & 0x07);
if (dev & 1<<4) printf("\tJTAG Active\n");
if (dev & 1<<5) printf("\tPWD Protect\n");
if (dev & 1<<6) printf("\tOTP\n");
if (dev & 1<<7) printf("\tDecrypt Enable\n");
if (dev & REG_STATUS_DONE) printf("\tDone Flag\n");
if (dev & REG_STATUS_ISC_EN) printf("\tISC Enable\n");
if (dev & 1 << 10) printf("\tWrite Enable\n");
if (dev & 1 << 11) printf("\tRead Enable\n");
if (dev & REG_STATUS_BUSY) printf("\tBusy Flag\n");
if (dev & REG_STATUS_FAIL) printf("\tFail Flag\n");
if (dev & 1 << 14) printf("\tFFEA OTP\n");
if (dev & 1 << 15) printf("\tDecrypt Only\n");
if (dev & 1 << 16) printf("\tPWD Enable\n");
if (dev & 1 << 17) printf("\tUFM OTP\n");
if (dev & 1 << 18) printf("\tASSP\n");
if (dev & 1 << 19) printf("\tSDM Enable\n");
if (dev & 1 << 20) printf("\tEncryption PreAmble\n");
if (dev & 1 << 21) printf("\tStd PreAmble\n");
if (dev & 1 << 22) printf("\tSPIm Fail1\n");
const uint8_t err = (dev >> 23)&0x07;
printf("\tBSE Error Code\n");
printf("\t\t");
switch (err) {
case 0:
printf("No err\n");
break;
case 1:
printf("ID ERR\n");
break;
case 2:
printf("CMD ERR\n");
break;
case 3:
printf("CRC ERR\n");
break;
case 4:
printf("Preamble ERR\n");
break;
case 5:
printf("Abort ERR\n");
break;
case 6:
printf("Overflow ERR\n");
break;
case 7:
printf("SDM EOF\n");
break;
case 8:
printf("Authentication ERR\n");
break;
case 9:
printf("Authentication Setup ERR\n");
break;
case 10:
printf("Bitstream Engine Timeout ERR\n");
break;
default:
printf("unknown error: %x\n", err);
}
if (dev & REG_STATUS_EXEC_ERR) printf("\tEXEC Error\n");
if ((dev >> 27) & 0x01) printf("\tDevice failed to verify\n");
if ((dev >> 28) & 0x01) printf("\tInvalid Command\n");
if ((dev >> 29) & 0x01) printf("\tSED Error\n");
if ((dev >> 30) & 0x01) printf("\tBypass Mode\n");
if ((dev >> 31) & 0x01) printf("\tFT Mode\n");
}

45
src/latticeSSPI.hpp Normal file
View File

@ -0,0 +1,45 @@
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright (C) 2025 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
*/
#ifndef SRC_LATTICESSPI_HPP_
#define SRC_LATTICESSPI_HPP_
#include <string>
#include "device.hpp"
#include "ftdispi.hpp"
class LatticeSSPI: public Device {
public:
LatticeSSPI(FtdiSpi *spi, const std::string &filename,
const std::string &file_type, const int8_t verbose);
uint32_t idCode() override;
int userCode();
void reset() override {}
void program(unsigned int offset, bool unprotect_flash) override;
bool protect_flash(uint32_t /*len*/) override { return false; }
bool unprotect_flash() override { return false; }
bool bulk_erase_flash() override { return false; }
private:
bool program_mem();
bool pollBusyFlag(bool verbose = false);
uint32_t readStatusReg();
void displayReadReg(uint32_t dev);
bool EnableISC(uint8_t flash_mode);
bool DisableISC();
bool flashErase(uint32_t mask);
bool checkStatus(uint32_t val, uint32_t mask) {
return ((readStatusReg() & mask) == val) ? true : false;
}
bool cmd_class_a(uint8_t cmd, uint8_t *rx, uint32_t len);
bool cmd_class_c(uint8_t cmd);
uint32_t char_array_to_word(const uint8_t *in);
FtdiSpi *_spi;
};
#endif // SRC_LATTICESSPI_HPP_

View File

@ -28,6 +28,7 @@
#include "gowin.hpp" #include "gowin.hpp"
#include "ice40.hpp" #include "ice40.hpp"
#include "lattice.hpp" #include "lattice.hpp"
#include "latticeSSPI.hpp"
#include "libusb_ll.hpp" #include "libusb_ll.hpp"
#include "jtag.hpp" #include "jtag.hpp"
#include "part.hpp" #include "part.hpp"
@ -284,9 +285,17 @@ int main(int argc, char **argv)
board->reset_pin, board->done_pin, board->oe_pin, board->reset_pin, board->done_pin, board->oe_pin,
args.verify, args.verbose); args.verify, args.verbose);
} else if (board->manufacturer == "lattice") { } else if (board->manufacturer == "lattice") {
target = new Ice40(spi, args.bit_file, args.file_type, if (board->fpga_part == "ice40") {
args.prg_type, target = new Ice40(spi, args.bit_file, args.file_type,
board->reset_pin, board->done_pin, args.verify, args.verbose); args.prg_type,
board->reset_pin, board->done_pin, args.verify, args.verbose);
} else if (board->fpga_part == "ecp5") {
target = new LatticeSSPI(spi, args.bit_file, args.file_type, args.verbose);
} else {
printError("Error (SPI mode): " + board->fpga_part +
" is an unsupported/unknown Lattice Model");
return EXIT_FAILURE;
}
} else if (board->manufacturer == "colognechip") { } else if (board->manufacturer == "colognechip") {
target = new CologneChip(spi, args.bit_file, args.file_type, args.prg_type, target = new CologneChip(spi, args.bit_file, args.file_type, args.prg_type,
board->reset_pin, board->done_pin, DBUS6, board->oe_pin, board->reset_pin, board->done_pin, DBUS6, board->oe_pin,