added support for Gowin GWU2X USB (JTAG mode) (#434)
This commit is contained in:
parent
7e90d071d9
commit
53578876d5
|
|
@ -12,6 +12,7 @@ else()
|
|||
option(ENABLE_UDEV "use udev to search JTAG adapter from /dev/xx" ON)
|
||||
endif()
|
||||
option(ENABLE_CMSISDAP "enable cmsis DAP interface (requires hidapi)" ON)
|
||||
option(ENABLE_GOWIN_GWU2X "enable Gowin GWU2X interface" ON)
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
option(ENABLE_LIBGPIOD "enable libgpiod bitbang driver (requires libgpiod)" ON)
|
||||
option(ENABLE_REMOTEBITBANG "enable remote bitbang driver" ON)
|
||||
|
|
@ -218,6 +219,16 @@ target_link_libraries(openFPGALoader
|
|||
${LIBFTDI_LIBRARIES}
|
||||
)
|
||||
|
||||
# Gowin GWU2X JTAG interface
|
||||
if(ENABLE_GOWIN_GWU2X)
|
||||
target_sources(openFPGALoader PRIVATE src/gwu2x_jtag.cpp)
|
||||
list (APPEND OPENFPGALOADER_HEADERS src/gwu2x_jtag.hpp)
|
||||
add_definitions(-DENABLE_GOWIN_GWU2X=1)
|
||||
message("Gowin GWU2X support enabled")
|
||||
else()
|
||||
message("Gowin GWU2X support disabled")
|
||||
endif()
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
|
||||
# winsock provides ntohs
|
||||
target_link_libraries(openFPGALoader ws2_32)
|
||||
|
|
|
|||
|
|
@ -67,6 +67,13 @@ gatemate_evb_spi:
|
|||
URL: https://colognechip.com/programmable-logic/gatemate/
|
||||
|
||||
|
||||
gwu2x:
|
||||
|
||||
- Name: gwu2x
|
||||
Description: Gowin GWUX2X
|
||||
URL: https://www.gowinsemi.com/en/product/detail/55/
|
||||
|
||||
|
||||
dfu:
|
||||
|
||||
- Name: DFU interface
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ enum communication_type {
|
|||
MODE_JETSONNANO_BITBANG, /*! Bitbang gpio pins */
|
||||
MODE_REMOTEBITBANG, /*! Remote Bitbang mode */
|
||||
MODE_CH347, /*! CH347 JTAG mode */
|
||||
MODE_GWU2X, /*! Gowin GWU2X JTAG mode */
|
||||
};
|
||||
|
||||
/*!
|
||||
|
|
@ -77,6 +78,8 @@ struct cable_t {
|
|||
/* Others cable configuration */
|
||||
#define CABLE_DEF(_type, _vid, _pid) \
|
||||
{_type, _vid, _pid, 0, 0, {}}
|
||||
#define CABLE_DEF_FULL(_type, _vid, _pid, _blv, _bld, _bhv, _bhd) \
|
||||
{_type, _vid, _pid, 0, 0, {0, _blv, _bld, _bhv, _bhd, 0, -1}}
|
||||
|
||||
static std::map <std::string, cable_t> cable_list = {
|
||||
// last 4 bytes are ADBUS7-0 value, ADBUS7-0 direction, ACBUS7-0 value, ACBUS7-0 direction
|
||||
|
|
@ -110,6 +113,7 @@ static std::map <std::string, cable_t> cable_list = {
|
|||
{"ft4232", FTDI_SER(0x0403, 0x6011, FTDI_INTF_A, 0x08, 0x0B, 0x08, 0x0B)},
|
||||
{"ft4232hp", FTDI_SER(0x0403, 0x6043, FTDI_INTF_A, 0x08, 0x0B, 0x00, 0x00)},
|
||||
{"ft4232hp_b", FTDI_SER(0x0403, 0x6043, FTDI_INTF_B, 0x08, 0x0B, 0x00, 0x00)},
|
||||
{"gwu2x", CABLE_DEF_FULL(MODE_GWU2X, 0x33AA, 0x0120, 0x02, 0x07, 0x0, 0x0)},
|
||||
{"ecpix5-debug", FTDI_SER(0x0403, 0x6010, FTDI_INTF_A, 0xF8, 0xFB, 0xFF, 0xFF)},
|
||||
{"jlink", CABLE_DEF(MODE_JLINK, 0x1366, 0x0105 )},
|
||||
{"jlink_base", CABLE_DEF(MODE_JLINK, 0x1366, 0x0101 )},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,456 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
* Copyright (C) 2024 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
|
||||
*/
|
||||
|
||||
#include <libusb.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "cable.hpp"
|
||||
#include "display.hpp"
|
||||
#include "gwu2x_jtag.hpp"
|
||||
#include "libusb_ll.hpp"
|
||||
|
||||
/*
|
||||
* TCK -> GPIOL0
|
||||
* TMS -> GPIOL1
|
||||
* TDI -> GPIOL2
|
||||
* TDO -> GPIOL3
|
||||
*/
|
||||
enum {
|
||||
GWU2X_TMS_LSB_WRO = 0x5B,
|
||||
GWU2X_TMS_LSB_RDWR = 0x5C,
|
||||
GWU2X_TCK = 0x9b,
|
||||
GWU2X_TDI_LSB_BIT_WRO = 0x6B,
|
||||
GWU2X_TDI_LSB_BIT_RDWR = 0x6C,
|
||||
GWU2X_TDI_LSB_BYTE_WRO = 0x7B,
|
||||
GWU2X_TDI_LSB_BYTE_RDWR = 0x7C,
|
||||
GWU2X_SET_FREQ_FAST = 0xAB,
|
||||
GWU2X_SET_FREQ_SLOW = 0xAC,
|
||||
GWU2X_READBACK_BUFFER_FORCED = 0x8B,
|
||||
GWU2X_READBACK_BUFFER = 0xDB, /* 0x11: LSB, 0xff: MSB */
|
||||
GWU2X_GPIO_CONF_LOW = 0x20, /* GPIO0-7 */
|
||||
GWU2X_GPIO_CONF_HIGH = 0x21, /* GPIO8-15 */
|
||||
GWU2X_GPIO_READ_LOW = 0x22, /* GPIO0-7 */
|
||||
GWU2X_GPIO_READ_HIGH = 0x23, /* GPIO8-15 */
|
||||
GWU2X_CPOL_SETTING = 0xCB,
|
||||
};
|
||||
|
||||
enum {
|
||||
READBACK_LSB = 0x11,
|
||||
READBACK_MSB = 0xff,
|
||||
};
|
||||
|
||||
GowinGWU2x::GowinGWU2x(cable_t *cable, uint32_t clkHz, int8_t verbose):
|
||||
libusb_ll(0, 0, verbose), _verbose(verbose > 1), _cable(cable),
|
||||
_usb_dev(nullptr), _dev(nullptr), _xfer_buf(nullptr), _xfer_pos(0),
|
||||
_buffer_len(256 + 2 + 1)
|
||||
{
|
||||
const int found = get_devices_list(_cable);
|
||||
if (found == 0)
|
||||
throw std::runtime_error("No cable found");
|
||||
if (found > 1)
|
||||
throw std::runtime_error("More than one cable found");
|
||||
std::vector<struct libusb_device *> dev_list = usb_dev_list();
|
||||
|
||||
/* here we have only one device present */
|
||||
_usb_dev = dev_list[0];
|
||||
|
||||
int ret = libusb_open(_usb_dev, &_dev);
|
||||
if (ret < 0)
|
||||
throw std::runtime_error("Failed to open device");
|
||||
|
||||
ret = libusb_claim_interface(_dev, 0);
|
||||
if (ret < 0) {
|
||||
char mess[256];
|
||||
snprintf(mess, 256, "Error claiming interface with error %s", libusb_error_name(ret));
|
||||
throw std::runtime_error(mess);
|
||||
}
|
||||
|
||||
_xfer_buf = new uint8_t[_buffer_len]; // one full TDI packet + readback cmd
|
||||
|
||||
/* cable configuration */
|
||||
if (!store_seq(GWU2X_GPIO_CONF_LOW, // gpio0-7
|
||||
cable->config.bit_low_dir, // direction
|
||||
cable->config.bit_low_val)) // value
|
||||
throw std::runtime_error("Error: low pins configuration failed");
|
||||
if (!store_seq(GWU2X_GPIO_CONF_HIGH, // gpio8-15
|
||||
cable->config.bit_high_dir, // direction
|
||||
cable->config.bit_high_val)) // value
|
||||
throw std::runtime_error("Error: high pins configuration failed");
|
||||
if (!xfer(nullptr, 0))
|
||||
throw std::runtime_error("Error: pin configuration failed");
|
||||
|
||||
if (setClkFreq(clkHz) < 0)
|
||||
throw std::runtime_error("Error: clock frequency configuration failed");
|
||||
}
|
||||
|
||||
GowinGWU2x::~GowinGWU2x()
|
||||
{
|
||||
flush();
|
||||
delete _xfer_buf;
|
||||
/* nothing about interface ? */
|
||||
libusb_close(_dev);
|
||||
}
|
||||
|
||||
int GowinGWU2x::writeTMS(const uint8_t *tms, uint32_t len, bool flush_buffer,
|
||||
const uint8_t tdi)
|
||||
{
|
||||
const uint8_t tdi_bit = (tdi) ? 0x80 : 0x00;
|
||||
uint8_t tms_buf = tdi_bit;
|
||||
uint8_t idx = 0; // bit index in tms_buf
|
||||
|
||||
/* As FTDI devices TMS instruction is 3 bytes long and can send up to
|
||||
* 7bits
|
||||
* 2nd byte tells number of TMS bits to send (0: 1bit, 6: 7bits)
|
||||
* 3rd byte is the sequence of TMS values LSB first. Offset 7 is the TDI
|
||||
* value for all TMS bits
|
||||
*/
|
||||
for (uint32_t pos = 0; pos < len; pos++) {
|
||||
const uint8_t tms_byte = tms[pos >> 3];
|
||||
const uint8_t bit_shift = pos & 0x07;
|
||||
const uint8_t tms_bit = (tms_byte >> bit_shift) & 0x01;
|
||||
if (tms_bit)
|
||||
tms_buf |= (1 << idx);
|
||||
idx += 1;
|
||||
/* if we have 7bits or if it's the last iteration
|
||||
* flush the buffer, and restart for the next sequence
|
||||
*/
|
||||
if (idx == 7 || pos == len - 1) {
|
||||
if (!store_seq(GWU2X_TMS_LSB_WRO, idx - 1, tms_buf))
|
||||
return -1;
|
||||
idx = 0;
|
||||
tms_buf = tdi_bit;
|
||||
}
|
||||
}
|
||||
|
||||
if (flush_buffer) {
|
||||
if (!xfer(nullptr, 0)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<int>(len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write / Read data in one to three steps
|
||||
* a sequence of up to 256 Bytes per packet
|
||||
* a sequence of up to 8 bits
|
||||
* the final bit (MSB) using TMS transition when end is true
|
||||
*/
|
||||
int GowinGWU2x::writeTDI(const uint8_t *tx, uint8_t *rx, uint32_t len, bool end)
|
||||
{
|
||||
const uint32_t real_len = len - (end ? 1 : 0); // if end: last bit is sent with TMS
|
||||
const uint32_t byte_len = real_len >> 3; // convert bit len to byte len (floor)
|
||||
const uint32_t bit_len = real_len & 0x07; // extract remaining bits
|
||||
uint8_t *tx_ptr = (uint8_t *)tx;
|
||||
uint8_t *rx_ptr = rx;
|
||||
|
||||
/* if the buffer is not empty, some tms bits are present
|
||||
* flush to keep maximum size available
|
||||
*/
|
||||
if (_xfer_pos != 0) {
|
||||
if (!xfer(nullptr, 0))
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 1. Byte sequence with up to 256 Bytes
|
||||
if (byte_len > 0) {
|
||||
uint32_t xfer_len = 256;
|
||||
for (uint32_t pos = 0; pos < byte_len; pos += xfer_len) {
|
||||
if (pos + 256 > byte_len)
|
||||
xfer_len = byte_len - pos;
|
||||
_xfer_buf[_xfer_pos++] = (rx) ? GWU2X_TDI_LSB_BYTE_RDWR : GWU2X_TDI_LSB_BYTE_WRO;
|
||||
_xfer_buf[_xfer_pos++] = xfer_len - 1;
|
||||
memcpy(&_xfer_buf[_xfer_pos], tx_ptr, xfer_len);
|
||||
_xfer_pos += xfer_len;
|
||||
if (rx)
|
||||
_xfer_buf[_xfer_pos++] = GWU2X_READBACK_BUFFER_FORCED;
|
||||
if (!xfer((rx) ? rx_ptr : nullptr, xfer_len))
|
||||
return -1;
|
||||
tx_ptr += xfer_len;
|
||||
if (rx_ptr)
|
||||
rx_ptr += xfer_len;
|
||||
}
|
||||
}
|
||||
|
||||
/* when end with a sequence of bits
|
||||
* don't do two usb transfers and
|
||||
* postpone it to end sequence
|
||||
*/
|
||||
const bool postponed_read = (bit_len != 0 && end);
|
||||
|
||||
// 2. Remaining bits between 1 and 7.
|
||||
if (bit_len != 0) {
|
||||
/* write up to 8 bits
|
||||
* may be less if end, but not more
|
||||
* the buffer read must be correctly aligned according to bit_len
|
||||
*/
|
||||
if (!store_seq(GWU2X_TDI_LSB_BIT_WRO + (rx != nullptr),
|
||||
static_cast<uint8_t>(bit_len - 1), *tx_ptr, !postponed_read && rx != nullptr))
|
||||
return -1;
|
||||
// When not end, flush buffer
|
||||
// NOTE: with a more clever global system why not
|
||||
// postning it for the next call ?
|
||||
if (!postponed_read) {
|
||||
/* unlike FTDI bits are filed LSB to MSB ie
|
||||
* for 2 bits: 0b000000XX
|
||||
* for 7 bits: 0b0XXXXXXX
|
||||
* no needs to shift here
|
||||
*/
|
||||
if (!xfer((rx) ? rx_ptr : nullptr, 1))
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. End using TMS instruction and last bit.
|
||||
if (end) {
|
||||
uint8_t rx_byte;
|
||||
/* we are in SHIFTDR or SHIFTIR -> move to next state */
|
||||
const uint8_t last_bit = (*tx_ptr >> bit_len) & 0x01;
|
||||
if (!store_seq(GWU2X_TMS_LSB_WRO + (rx != nullptr), 0,
|
||||
static_cast<uint8_t>(((last_bit) ? 0x80 : 0x00) | 0x01),
|
||||
rx != nullptr))
|
||||
return -1;
|
||||
if (!xfer((rx)? &rx_byte : nullptr, 1))
|
||||
return -1;
|
||||
if (rx) {
|
||||
if (postponed_read) {
|
||||
*rx_ptr = rx_byte;
|
||||
} else {
|
||||
const uint8_t tdo_bit = 1 << (bit_len);
|
||||
if (rx_byte & 0x01) // here only one bit is read (always LSB)
|
||||
*rx_ptr |= tdo_bit;
|
||||
else
|
||||
*rx_ptr &= ~tdo_bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
return static_cast<int>(len);
|
||||
}
|
||||
|
||||
int GowinGWU2x::toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len)
|
||||
{
|
||||
/* Gowin GWU2X has a specific command to produces
|
||||
* a sequence sequence to up to 65536 cycles
|
||||
* 0: 1 clk cycle, 0xffff: 65535 clk cycles
|
||||
*/
|
||||
/* No need to check/flush buffer will be done by store_seq */
|
||||
uint16_t len = 0;
|
||||
if (_verbose)
|
||||
printf("toggleClk : %02x %02x %u\n", tms, tdi, clk_len);
|
||||
|
||||
for (uint32_t length = clk_len; length > 0; length -= len) {
|
||||
len = ((length > 65536) ? 65536 : length) - 1;
|
||||
if (!store_seq(GWU2X_TCK,
|
||||
static_cast<uint8_t>((len >> 0) & 0xff),
|
||||
static_cast<uint8_t>((len >> 8) & 0xff)))
|
||||
return -1;
|
||||
len += 1;
|
||||
}
|
||||
/* flush before return */
|
||||
if (_xfer_pos != 0) {
|
||||
if (!xfer(nullptr, 0))
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int>(clk_len);
|
||||
}
|
||||
|
||||
enum {
|
||||
EP_IN = 0x81,
|
||||
EP_OUT = 0x02,
|
||||
};
|
||||
|
||||
/* when readback we consider it's for the current sequence, not
|
||||
* for current content
|
||||
*/
|
||||
bool GowinGWU2x::store_seq(const uint8_t &opcode, const uint8_t &len,
|
||||
const uint8_t &data, const bool readback)
|
||||
{
|
||||
if (_verbose) {
|
||||
char message[256];
|
||||
snprintf(message, 256, "store seq %02x %02x %02x %d",
|
||||
opcode, len, data, readback);
|
||||
printInfo(message);
|
||||
}
|
||||
const size_t xfer_len = 3 + readback;
|
||||
if (_xfer_pos + xfer_len > _buffer_len) {
|
||||
if (!xfer(nullptr, 0))
|
||||
return false;
|
||||
}
|
||||
_xfer_buf[_xfer_pos++] = opcode;
|
||||
_xfer_buf[_xfer_pos++] = len;
|
||||
_xfer_buf[_xfer_pos++] = data;
|
||||
if (readback)
|
||||
_xfer_buf[_xfer_pos++] = GWU2X_READBACK_BUFFER_FORCED;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GowinGWU2x::xfer(uint8_t *rx, uint16_t rx_len, const uint16_t timeout)
|
||||
{
|
||||
int actual_length;
|
||||
int ret;
|
||||
if (_xfer_pos == 0) // nothing to do
|
||||
return true;
|
||||
|
||||
if (_verbose) {
|
||||
printInfo("Write " + std::to_string(_xfer_pos) + " Bytes");
|
||||
printf("\t");
|
||||
for (uint32_t i = 0; i < _xfer_pos; i++) {
|
||||
char message[6];
|
||||
snprintf(message, 6, "0x%02x ", _xfer_buf[i]);
|
||||
printSuccess(message, false);
|
||||
}
|
||||
printf("\n");
|
||||
displayCmd();
|
||||
}
|
||||
|
||||
ret = libusb_bulk_transfer(_dev, EP_OUT,
|
||||
_xfer_buf, _xfer_pos, &actual_length, timeout);
|
||||
if (ret < 0) {
|
||||
printError("Write failed with error " + std::to_string(ret));
|
||||
return false;
|
||||
}
|
||||
_xfer_pos = 0;
|
||||
if (!rx) // message sent, nothing to read -> quit
|
||||
return true;
|
||||
uint8_t *rx_ptr = rx;
|
||||
uint32_t dummy;
|
||||
ret = libusb_bulk_transfer(_dev, 0x83, (uint8_t*)&dummy, 4, &actual_length, timeout);
|
||||
if (_verbose)
|
||||
printf("ret: %d %u %d\n", ret, dummy, actual_length);
|
||||
do {
|
||||
ret = libusb_bulk_transfer(_dev, EP_IN, rx_ptr, rx_len,
|
||||
&actual_length, timeout);
|
||||
if (ret < 0) {
|
||||
char message[256];
|
||||
snprintf(message, 256, "Failed to read: %d %s\n", ret,
|
||||
libusb_strerror(static_cast<libusb_error>(ret)));
|
||||
printError(message);
|
||||
return false;
|
||||
}
|
||||
if (_verbose) {
|
||||
printf("%d %d %d\n", ret, rx_len, actual_length);
|
||||
for (int ii = 0; ii < rx_len; ii++)
|
||||
printf("%02x ", rx_ptr[ii]);
|
||||
printf("\n");
|
||||
}
|
||||
rx_ptr += actual_length;
|
||||
rx_len -= actual_length;
|
||||
} while (rx_len > 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GowinGWU2x::setClkFreq(uint32_t freqHz)
|
||||
{
|
||||
if (freqHz < 120e3 || freqHz > 30e6) {
|
||||
printError("clk Frequency must be between 120kHz and 30MHz");
|
||||
return -1;
|
||||
}
|
||||
const uint16_t div = static_cast<uint16_t>(60e6 / freqHz);
|
||||
const int real_freq = static_cast<int>(60e6 / div);
|
||||
printInfo("User requested: " + std::to_string(freqHz) + " real frequency is "
|
||||
+ std::to_string(real_freq));
|
||||
_xfer_buf[_xfer_pos++] = GWU2X_SET_FREQ_FAST + ((freqHz < 240e3) ? 1 : 0);
|
||||
_xfer_buf[_xfer_pos++] = static_cast<uint8_t>(div - ((freqHz < 240e3) ? 256 : 0));
|
||||
if (!xfer(nullptr, 0, 1000))
|
||||
return -1;
|
||||
_clkHZ = real_freq;
|
||||
return static_cast<int>(_clkHZ);
|
||||
}
|
||||
|
||||
void GowinGWU2x::displayCmd()
|
||||
{
|
||||
uint32_t len = 0;
|
||||
while (len < _xfer_pos) {
|
||||
const uint8_t opcode = _xfer_buf[len++];
|
||||
const uint8_t b_len = _xfer_buf[len++];
|
||||
uint8_t tdi;
|
||||
uint8_t tms;
|
||||
char message[256];
|
||||
uint8_t bytes[256];
|
||||
switch(opcode) {
|
||||
case GWU2X_TMS_LSB_WRO:
|
||||
tdi = (_xfer_buf[len] >> 7) & 0x01;
|
||||
tms = (_xfer_buf[len++] & 0x7f);
|
||||
snprintf(message, 256,
|
||||
"TMS Write Only len %d TDI: %x TMS: %02x",
|
||||
b_len + 1, tdi, tms);
|
||||
break;
|
||||
case GWU2X_TMS_LSB_RDWR:
|
||||
tdi = (_xfer_buf[len] >> 7) & 0x01;
|
||||
tms = (_xfer_buf[len++] & 0x7f);
|
||||
snprintf(message, 256,
|
||||
"TMS Read/Write len %d TDI: %x TMS: %02x",
|
||||
b_len + 1, tdi, tms);
|
||||
break;
|
||||
case GWU2X_TDI_LSB_BIT_WRO:
|
||||
tdi = _xfer_buf[len++];
|
||||
snprintf(message, 256,
|
||||
"TDI bit Write Only len %d TDI: %2x",
|
||||
b_len + 1, tdi);
|
||||
break;
|
||||
case GWU2X_TDI_LSB_BIT_RDWR:
|
||||
tdi = _xfer_buf[len++];
|
||||
snprintf(message, 256,
|
||||
"TDI bit Read/Write len %d TDI: %2x",
|
||||
b_len + 1, tdi);
|
||||
break;
|
||||
case GWU2X_TDI_LSB_BYTE_WRO:
|
||||
memcpy(bytes, _xfer_buf, b_len + 1);
|
||||
len += b_len + 1;
|
||||
snprintf(message, 256,
|
||||
"TDI Byte Write Only len %d TDI: %2x",
|
||||
b_len + 1, bytes[0]);
|
||||
break;
|
||||
case GWU2X_TDI_LSB_BYTE_RDWR:
|
||||
memcpy(bytes, _xfer_buf, b_len + 1);
|
||||
len += b_len + 1;
|
||||
snprintf(message, 256,
|
||||
"TDI Byte Read/Write len %d TDI: %2x",
|
||||
b_len + 1, bytes[0]);
|
||||
break;
|
||||
case GWU2X_TCK:
|
||||
tdi = _xfer_buf[len++];
|
||||
snprintf(message, 256,
|
||||
"toggle Clock len %d",
|
||||
(b_len | (tdi << 8)) + 1);
|
||||
break;
|
||||
|
||||
case GWU2X_GPIO_CONF_HIGH:
|
||||
tms = _xfer_buf[len++];
|
||||
snprintf(message, 256,
|
||||
"GPIO conf high: direction: %x value: %02x",
|
||||
b_len, tms);
|
||||
break;
|
||||
case GWU2X_GPIO_CONF_LOW:
|
||||
tms = _xfer_buf[len++];
|
||||
snprintf(message, 256,
|
||||
"GPIO conf low: direction: %x value: %02x",
|
||||
b_len, tms);
|
||||
break;
|
||||
case GWU2X_SET_FREQ_FAST:
|
||||
case GWU2X_SET_FREQ_SLOW:
|
||||
snprintf(message, 256,
|
||||
"Set clk freq prescaler: %02x",
|
||||
b_len);
|
||||
break;
|
||||
case GWU2X_READBACK_BUFFER_FORCED:
|
||||
len--;
|
||||
snprintf(message, 256, "readback buffer");
|
||||
break;
|
||||
default:
|
||||
snprintf(message, 256, "Unknown");
|
||||
printError(message);
|
||||
return;
|
||||
}
|
||||
printInfo(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
* Copyright (C) 2024 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
|
||||
*/
|
||||
|
||||
#ifndef SRC_GWU2X_JTAG_HPP__
|
||||
#define SRC_GWU2X_JTAG_HPP__
|
||||
|
||||
#include <libusb.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "cable.hpp"
|
||||
#include "jtagInterface.hpp"
|
||||
#include "libusb_ll.hpp"
|
||||
|
||||
class GowinGWU2x: public JtagInterface, private libusb_ll
|
||||
{
|
||||
public:
|
||||
GowinGWU2x(cable_t *cable, uint32_t clkHz, int8_t verbose);
|
||||
~GowinGWU2x();
|
||||
|
||||
int setClkFreq(uint32_t clkHz) override;
|
||||
|
||||
/*!
|
||||
* \brief flush TMS internal buffer (ie. transmit to converter)
|
||||
* \param tdo: pointer for read operation. May be NULL
|
||||
* \param len: number of bit to send
|
||||
* \return number of bit send/received
|
||||
*/
|
||||
int writeTMS(const uint8_t *tms, uint32_t len, bool flush_buffer, const uint8_t tdi = 1) override;
|
||||
|
||||
/*!
|
||||
* \brief send TDI bits (mainly in shift DR/IR state)
|
||||
* \param tdi: array of TDI values (used to write)
|
||||
* \param tdo: array of TDO values (used when read)
|
||||
* \param len: number of bit to send/receive
|
||||
* \param end: in JTAG state machine last bit and tms are set in same time
|
||||
* but only in shift[i|d]r, if end is false tms remain the same.
|
||||
* \return number of bit written and/or read
|
||||
*/
|
||||
int writeTDI(const uint8_t *tx, uint8_t *rx, uint32_t len, bool end) override;
|
||||
|
||||
/*!
|
||||
* \brief toggle clk without touch of TDI/TMS
|
||||
* \param tms: state of tms signal
|
||||
* \param tdi: state of tdi signal
|
||||
* \param clk_len: number of clock cycle
|
||||
* \return number of clock cycle send
|
||||
*/
|
||||
int toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len) override;
|
||||
|
||||
/*!
|
||||
* \brief return internal buffer size (in byte)
|
||||
* \return internal buffer size
|
||||
*/
|
||||
int get_buffer_size() override {return static_cast<int>(_buffer_len);}
|
||||
|
||||
/*!
|
||||
* \brief return status of internal buffer
|
||||
* \return true when internal buffer is full
|
||||
*/
|
||||
bool isFull() override {return _xfer_pos == _buffer_len;}
|
||||
|
||||
/*!
|
||||
* \brief force internal flush buffer
|
||||
* \return 1 if success, 0 if nothing to write, -1 is something wrong
|
||||
*/
|
||||
int flush() override {
|
||||
if (_xfer_pos == 0)
|
||||
return 0;
|
||||
return xfer(nullptr, 0) ? 1 : -1;
|
||||
}
|
||||
private:
|
||||
bool xfer(uint8_t *rx, uint16_t rx_len, uint16_t timeout = 1000);
|
||||
bool store_seq(const uint8_t &opcode, const uint8_t &len,
|
||||
const uint8_t &data, const bool readback = false);
|
||||
bool init_device();
|
||||
void displayCmd(); // debug purpose: translate sequence to human
|
||||
// readable sequence
|
||||
bool _verbose;
|
||||
cable_t *_cable;
|
||||
struct libusb_device *_usb_dev;
|
||||
struct libusb_device_handle *_dev;
|
||||
uint8_t *_xfer_buf; /* internal buffer */
|
||||
uint32_t _xfer_pos; /* number of Bytes already stored in _xfer_buf */
|
||||
uint32_t _buffer_len; /* _xfer_buf capacity (Byte) */
|
||||
};
|
||||
|
||||
#endif // SRC_GWU2X_JTAG_HPP__
|
||||
11
src/jtag.cpp
11
src/jtag.cpp
|
|
@ -18,6 +18,9 @@
|
|||
#include "jtag.hpp"
|
||||
#include "ftdiJtagBitbang.hpp"
|
||||
#include "ftdiJtagMPSSE.hpp"
|
||||
#ifdef ENABLE_GOWIN_GWU2X
|
||||
#include "gwu2x_jtag.hpp"
|
||||
#endif
|
||||
#ifdef ENABLE_LIBGPIOD
|
||||
#include "libgpiodJtagBitbang.hpp"
|
||||
#endif
|
||||
|
|
@ -108,6 +111,14 @@ Jtag::Jtag(const cable_t &cable, const jtag_pins_conf_t *pin_conf,
|
|||
case MODE_DIRTYJTAG:
|
||||
_jtag = new DirtyJtag(clkHZ, verbose);
|
||||
break;
|
||||
#ifdef ENABLE_GOWIN_GWU2X
|
||||
case MODE_GWU2X:
|
||||
_jtag = new GowinGWU2x((cable_t *)&cable, clkHZ, verbose);
|
||||
break;
|
||||
#else
|
||||
std::cerr << "Jtag: support for Gowin GWU2X was not enabled at compile time" << std::endl;
|
||||
throw std::exception();
|
||||
#endif
|
||||
case MODE_JLINK:
|
||||
_jtag = new Jlink(clkHZ, verbose, cable.vid, cable.pid);
|
||||
break;
|
||||
|
|
|
|||
Loading…
Reference in New Issue