Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
907f662eeThis file is pinned to the selected release. Download the original to open it in your design tool.
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright (C) 2020 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
*/
#include <libusb.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <vector>
#include "display.hpp"
#include "ftdiJtagMPSSE.hpp"
#include "ftdipp_mpsse.hpp"
using namespace std;
#define DEBUG 0
#ifdef DEBUG
#define display(...) \
do { \
if (_verbose) fprintf(stdout, __VA_ARGS__); \
}while(0)
#else
#define display(...) do {}while(0)
#endif
FtdiJtagMPSSE::FtdiJtagMPSSE(const cable_t &cable,
const string &dev, const string &serial, uint32_t clkHZ,
bool invert_read_edge, int8_t verbose):
FTDIpp_MPSSE(cable, dev, serial, clkHZ, verbose), _ch552WA(false),
_cmd8EWA(false),
_write_mode(MPSSE_WRITE_NEG), // always write on neg edge
_read_mode(0),
_invert_read_edge(invert_read_edge), // false: pos, true: neg
_tdo_pos(0)
{
init_internal(cable.config);
}
FtdiJtagMPSSE::~FtdiJtagMPSSE()
{
int read;
/* Before shutdown, we must wait until everything is shifted out
* Do this by temporary enabling loopback mode, write something
* and wait until we can read it back
*/
static unsigned char tbuf[16] = { SET_BITS_LOW, 0xff, 0x00,
SET_BITS_HIGH, 0xff, 0x00,
LOOPBACK_START,
static_cast<unsigned char>(MPSSE_DO_READ | _read_mode |
MPSSE_DO_WRITE | _write_mode | MPSSE_LSB),
0x04, 0x00,
0xaa, 0x55, 0x00, 0xff, 0xaa,
LOOPBACK_END
};
mpsse_store(tbuf, 16);
read = mpsse_read(tbuf, 5);
if (read != 5)
fprintf(stderr,
"Loopback failed, expect problems on later runs %d\n", read);
}
void FtdiJtagMPSSE::init_internal(const mpsse_bit_config &cable)
{
display("iProduct : %s\n", _iproduct);
if (!strncmp((const char *)_iproduct, "Sipeed-Debug", 12)) {
_ch552WA = true;
}
// This Sipeed firmware does not support MPSEE 0x8E, 0x8F commands properly
if ( (!strncmp((const char *)_imanufacturer, "SIPEED", 6))
&& (!strncmp((const char *)_iserialnumber, "2023112818", 10)) ) {
_cmd8EWA = true;
}
display("%x\n", cable.bit_low_val);
display("%x\n", cable.bit_low_dir);
display("%x\n", cable.bit_high_val);
display("%x\n", cable.bit_high_dir);
if (init(5, 0xfb, BITMODE_MPSSE) != 0)
throw std::runtime_error("low level FTDI init failed");
config_edge();
_curr_tms = (cable.bit_low_val >> 3) & 0x01;
_curr_tdi = (cable.bit_low_val >> 1) & 0x01;
}
int FtdiJtagMPSSE::setClkFreq(uint32_t clkHZ)
{
int ret = FTDIpp_MPSSE::setClkFreq(clkHZ);
config_edge();
return ret;
}
void FtdiJtagMPSSE::config_edge()
{
/* at high (>15MHz) with digilent cable (arty)
* opposite edges must be used.
* Not required with classic FT2232 but user selectable
*/
if (_invert_read_edge || (FTDIpp_MPSSE::getClkFreq() >= 15000000 &&
!strncmp((const char *)_iproduct, "Digilent USB Device", 19))) {
_read_mode = MPSSE_READ_NEG;
} else {
_read_mode = 0;
}
}
int FtdiJtagMPSSE::writeTMS(const uint8_t *tms, uint32_t len, bool flush_buffer, const uint8_t tdi)
{
(void) flush_buffer;
display("%s %u %u\n", __func__, len, (len/8)+1);
uint8_t curr_tdi = (tdi << 7);
if (len == 0)
return 0;
int xfer = len;
int iter = _buffer_size / 3;
int offset = 0, pos = 0;
uint8_t buf[3]= {static_cast<unsigned char>(MPSSE_WRITE_TMS | MPSSE_LSB |
MPSSE_BITMODE | _write_mode),
0, 0};
while (xfer > 0) {
uint8_t curr_tms = 0;
int bit_to_send = (xfer > 6) ? 6 : xfer;
buf[1] = bit_to_send-1;
buf[2] = curr_tdi;
for (int i = 0; i < bit_to_send; i++, offset++) {
curr_tms = ((tms[offset >> 3] & (1 << (offset & 0x07))) ? 1 : 0);
buf[2] |= (curr_tms << i);
}
buf[2] |= (curr_tms << bit_to_send);
pos+=3;
mpsse_store(buf, 3);
if (pos == iter * 3) {
pos = 0;
if (mpsse_write() < 0)
printf("writeTMS: error\n");
if (_ch552WA) {
uint8_t c[len/8+1];
int ret = ftdi_read_data(_ftdi, c, len/8+1);
if (ret != 0) {
printf("ret : %d\n", ret);
}
}
}
xfer -= bit_to_send;
}
if (flush_buffer)
mpsse_write();
if (_ch552WA) {
uint8_t c[len/8+1];
ftdi_read_data(_ftdi, c, len/8+1);
}
return len;
}
/* need a WA for ch552 */
int FtdiJtagMPSSE::toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len)
{
(void) tdi;
int ret;
uint32_t len = clk_len;
/* clk ouput without data xfer is only supported
* with 2232H, 4242H & 232H
*/
if ((_ftdi->type == TYPE_2232H || _ftdi->type == TYPE_4232H ||
_ftdi->type == TYPE_232H) && !_cmd8EWA) {
uint8_t buf[] = {static_cast<uint8_t>(0x8f), 0, 0};
while (len) {
unsigned int chunk = len;
if (chunk > 0x10000 * 8)
chunk = 0x10000 * 8;
if (chunk > 8) {
unsigned cycles8 = chunk / 8;
len -= cycles8 * 8;
cycles8--;
buf[1] = ((cycles8) ) & 0xff;
buf[2] = ((cycles8) >> 8) & 0xff;
mpsse_store(buf, 3);
}
if (len && len < 9) {
buf[0] = 0x8E;
buf[1] = len - 1;
mpsse_store(buf, 2);
len = 0;
}
}
ret = clk_len;
} else {
int byteLen = (len+7)/8;
uint8_t buf_tms[byteLen];
memset(buf_tms, (tms) ? 0xff : 0x00, byteLen);
ret = writeTMS(buf_tms, len, false);
}
return ret;
}
int FtdiJtagMPSSE::flush()
{
return mpsse_write();
}
int FtdiJtagMPSSE::writeTDI(const uint8_t *tdi, uint8_t *tdo, uint32_t len, bool last)
{
/* 3 possible case :
* - n * 8bits to send -> use byte command
* - less than 8bits -> use bit command
* - last bit to send -> sent in conjunction with TMS
*/
int tx_buff_size = mpsse_get_buffer_size();
int real_len = (last) ? len - 1 : len; // if its a buffer in a big send send len
// else suppress last bit -> with TMS
int nb_byte = real_len >> 3; // number of byte to send
int nb_bit = (real_len & 0x07); // residual bits
int xfer = tx_buff_size - 3;
unsigned char c[xfer];
unsigned char *rx_ptr = (unsigned char *)tdo;
unsigned char *tx_ptr = (unsigned char *)tdi;
unsigned char tx_buf[3] = {(unsigned char)(MPSSE_LSB |
((tdi) ? (MPSSE_DO_WRITE | _write_mode) : 0) |
((tdo) ? (MPSSE_DO_READ | _read_mode) : 0)),
static_cast<unsigned char>((xfer - 1) & 0xff), // low
static_cast<unsigned char>((((xfer - 1) >> 8) & 0xff))}; // high
display("%s len : %u %d %d %d last: %d\n", __func__, len, real_len, nb_byte,
nb_bit, last);
if ((nb_byte + _num + 3) > _buffer_size)
mpsse_write();
if ((nb_byte * 8) + nb_bit != real_len) {
printf("pas cool\n");
throw std::exception();
}
/* if only one full byte use BITMODE to reduce
* transaction size
*/
if (nb_byte == 1 && nb_bit == 0) {
nb_byte = 0;
nb_bit = 8;
}
while (nb_byte != 0) {
int xfer_len = (nb_byte > xfer) ? xfer : nb_byte;
tx_buf[1] = (((xfer_len - 1) ) & 0xff); // low
tx_buf[2] = (((xfer_len - 1) >> 8) & 0xff); // high
mpsse_store(tx_buf, 3);
if (tdi) {
mpsse_store(tx_ptr, xfer_len);
tx_ptr += xfer_len;
}
if (tdo) {
mpsse_read(rx_ptr, xfer_len);
rx_ptr += xfer_len;
} else if (_ch552WA) {
mpsse_write();
ftdi_read_data(_ftdi, c, xfer_len);
} else if (!last) {
mpsse_write();
}
nb_byte -= xfer_len;
}
unsigned char last_bit = (tdi) ? *tx_ptr : 0;
// never double write when nb_bit == 0
bool double_write = (nb_bit != 0) ? true : false;
if (nb_bit != 0) {
display("%s read/write %d bit\n", __func__, nb_bit);
tx_buf[0] |= MPSSE_BITMODE;
tx_buf[1] = nb_bit - 1;
mpsse_store(tx_buf, 2);
if (tdi) {
display("%s last_bit %x size %d\n", __func__, last_bit, nb_bit-1);
mpsse_store(last_bit);
}
if (tdo && !last) {
mpsse_read(rx_ptr, 1);
double_write = false;
/* realign we have read nb_bit
* since LSB add bit by the left and shift
* we need to complete shift
*/
*rx_ptr >>= (8 - nb_bit);
display("%s %x\n", __func__, *rx_ptr);
} else if (_ch552WA) {
if (tdo) {
mpsse_read(rx_ptr, 1);
double_write = false;
*rx_ptr >>= (8 - nb_bit);
} else {
mpsse_write();
ftdi_read_data(_ftdi, c, nb_bit);
}
} else if (!last) {
mpsse_write();
}
}
/* display : must be dropped */
if (_verbose && tdo) {
display("\n");
for (int i = (len / 8) - 1; i >= 0; i--)
display("%x ", (unsigned char)tdo[i]);
display("\n");
}
if (last == 1) {
last_bit = (tdi)? (*tx_ptr & (1 << nb_bit)) : 0;
display("%s move to EXIT1_xx and send last bit %x\n", __func__, (last_bit?0x81:0x01));
/* write the last bit in conjunction with TMS */
tx_buf[0] = MPSSE_WRITE_TMS | MPSSE_LSB | MPSSE_BITMODE | _write_mode |
((tdo) ? (MPSSE_DO_READ | _read_mode) : 0);
tx_buf[1] = 0x0; // send 1bit
tx_buf[2] = ((last_bit) ? 0x81 : 0x01); // we know in TMS tdi is bit 7
// and to move to EXIT_XR TMS = 1
mpsse_store(tx_buf, 3);
if (tdo) {
unsigned char c[2];
int index = 0;
mpsse_read(c, 1 + ((double_write)?1:0));
if (double_write) {
*rx_ptr = c[index] >> (8-nb_bit);
index++;
}
/* in this case for 1 one it's always bit 7 */
*rx_ptr |= (((c[index]) & 0x80) >> (7 - nb_bit));
} else if (_ch552WA) {
mpsse_write();
ftdi_read_data(_ftdi, c, 1);
} else {
mpsse_write();
}
}
return 0;
}
int32_t FtdiJtagMPSSE::update_tms_buff(uint8_t *buffer, uint8_t bit,
uint32_t offset, uint8_t tdi, uint8_t *tdo, bool end)
{
int32_t ret;
if (_verbose)
printf("%s %u %02x %d\n", __func__, offset, buffer[0], end);
if (!end) {
uint8_t bit_shift = (1 << (offset));
if (bit)
buffer[0] |= bit_shift;
else
buffer[0] &= ~bit_shift;
offset++;
}
if (offset == 6 || end) {
if (tdi)
buffer[0] |= (0x01 << 7);
else
buffer[0] &= ~(0x01 << 7);
uint8_t mp[3] = {
static_cast<unsigned char>(MPSSE_WRITE_TMS | MPSSE_LSB |
MPSSE_BITMODE | _write_mode |
MPSSE_DO_READ | _read_mode),
static_cast<uint8_t>(offset - 1),
buffer[0]
};
// force a write
if (_verbose)
printf("\t%02x %02d %02x\n", mp[0], mp[1], mp[2]);
if ((ret = mpsse_store(mp, 3)) < 0)
return ret;
uint8_t tdo_tmp;
if ((ret = mpsse_read(&tdo_tmp, 1)) < 0)
return ret;
update_tdo_buff(&tdo_tmp, tdo, offset);
offset = 0;
buffer[0] = 0;
}
return offset;
}
uint32_t FtdiJtagMPSSE::update_tdo_buff(uint8_t *buffer, uint8_t *tdo, uint32_t len)
{
if (_verbose) {
printError("update tdo " + std::to_string(_tdo_pos) + " " + std::to_string(len) + " ", false);
uint32_t tt = (len + 7) / 8;
for (uint32_t i = 0; i < tt; i++)
printf("%02x ", buffer[i]);
}
for (uint32_t i = 0; i < len; i++, _tdo_pos++) {
uint8_t bit = (buffer[i >> 3] >> (i & 0x07)) & 0x01;
uint8_t mask = 1 << (_tdo_pos & 0x07);
if (bit)
tdo[_tdo_pos >> 3] |= mask;
else
tdo[_tdo_pos >> 3] &= ~mask;
}
if (_verbose)
printf("\n");
return _tdo_pos;
}
bool FtdiJtagMPSSE::writeTMSTDI(const uint8_t *tms, const uint8_t *tdi,
uint8_t *tdo, uint32_t len)
{
int32_t ret;
uint32_t max_len = 1024;
uint8_t mode = 0; // current state: 0 none, 1 TDI, 2 TMS
uint8_t tdi_buf[max_len]; // buffer to store TDI sequence
uint8_t tms_tmp = 0; // buffer to store TMS sequence (limited to 6bits per cmd)
uint8_t tdo_tmp[max_len]; // local TDO sequence
uint32_t buff_len = 0; // current bits stored
memset(tdi_buf, 0, max_len);
memset(tdo_tmp, 0, max_len);
_tdo_pos = 0; // current bits read
if (_verbose)
printSuccess("begin: " + std::to_string(len));
for (uint32_t buf_pos = 0; buf_pos < len; buf_pos++) {
/* extract bit from TMS and TDI sequence */
uint8_t tms_bit = (tms[buf_pos >> 3] >> (buf_pos & 0x07) & 0x01);
uint8_t tdi_bit = (tdi[buf_pos >> 3] >> (buf_pos & 0x07) & 0x01);
if (_verbose) {
char mess[256];
snprintf(mess, 256, "tms %d -> %d tdi %d -> %u mode %u %u/%u (%u)",
_curr_tms, tms_bit, _curr_tdi, tdi_bit, mode, buf_pos, len, buff_len);
printInfo(mess);
}
/* possible case:
* tdi & tms not changed -> write tdi
* tdi change but tms not -> write tdi
* tdi idem but tms changed -> write tms
* tdi & tms changed -> serie of write tms
* but finally:
* if tms is not changed -> write tdi
* otherwise -> write tms
*/
/* tms unchanged -> try to use TDI/TDO transaction */
if (tms_bit == _curr_tms) {
/* a tms transaction exist but not flushed or full
* if tdi is not changed -> update tms sequence
*/
if (mode == 2 && buff_len != 0 && tdi_bit == _curr_tdi) {
ret = update_tms_buff(&tms_tmp, tms_bit, buff_len,
tdi_bit, tdo);
if (ret < 0)
return false;
else
buff_len = ret;
} else {
/* tdi sequence
* first flush tms sequence if required */
if (mode != 1 && buff_len != 0) {
ret = update_tms_buff(&tms_tmp, 0, buff_len, _curr_tdi,
tdo, true);
if (ret < 0)
return false;
else
buff_len = ret;
}
/* update tdi buffer with one more bit */
uint8_t mask = (1 << (buff_len & 0x07));
if (tdi_bit)
tdi_buf[buff_len >> 3] |= mask;
else
tdi_buf[buff_len >> 3] &= ~mask;
buff_len++;
mode = 1;
}
/* TMS is changed -> TMS transaction */
} else {
/* flush */
/* previous write TDI -> flush */
if (mode == 1 && buff_len > 0) {
bool is_end = false;
/* tms 0 -> 1: it's handled by writeTDI:
* append bit to avoid another transaction */
if (_curr_tms == 0 && tms_bit == 1) {
uint8_t mask = (1 << (buff_len & 0x07));
if (tdi_bit)
tdi_buf[buff_len >> 3] |= mask;
else
tdi_buf[buff_len >> 3] &= ~mask;
buff_len++;
is_end = true;
}
writeTDI(tdi_buf, tdo_tmp, buff_len, is_end);
update_tdo_buff(tdo_tmp, tdo, buff_len);
memset(tdi_buf, 0, max_len);
buff_len = 0;
if (is_end) {
_curr_tdi = tdi_bit;
mode = 1;
continue;
}
/* same state -> simply flush */
} else if (tdi_bit != _curr_tdi && mode == 2 && buff_len > 0) {
ret = update_tms_buff(&tms_tmp, 0, buff_len, _curr_tdi, tdo, true);
if (ret < 0)
return false;
else
buff_len = ret;
tms_tmp = 0;
}
/* update */
ret = update_tms_buff(&tms_tmp, tms_bit, buff_len, tdi_bit, tdo);
if (ret < 0)
return false;
else
buff_len = ret;
mode = 2;
}
/* buffer full? */
if (buff_len == 8*max_len && mode == 1) {
writeTDI(tdi_buf, tdo_tmp, buff_len, false);
update_tdo_buff(tdo_tmp, tdo, buff_len);
memset(tdi_buf, 0, max_len);
buff_len = 0;
} else if (buff_len == 6 && mode == 2) {
ret = update_tms_buff(&tms_tmp, 0, buff_len, _curr_tdi, tdo, true);
if (ret < 0)
return false;
else
buff_len = ret;
tms_tmp = 0;
}
_curr_tdi = tdi_bit;
_curr_tms = tms_bit;
}
/* end -> force flush buffers */
if (buff_len > 0) {
switch (mode) {
case 1:
writeTDI(tdi_buf, tdo_tmp, buff_len, false);
update_tdo_buff(tdo_tmp, tdo, buff_len);
break;
case 2:
if (update_tms_buff(&tms_tmp, 0, buff_len, _curr_tdi,
tdo, true) < 0)
return false;
break;
}
}
if (_verbose) {
printSuccess("end state: tdi " + std::to_string(_curr_tdi) +
" tms " + std::to_string(_curr_tms));
}
return true;
}