diff --git a/CMakeLists.txt b/CMakeLists.txt index 9540a9b..ee0ff13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ set(OPENFPGALOADER_SOURCE src/anlogic.cpp src/anlogicBitParser.cpp src/anlogicCable.cpp + src/ch552_jtag.cpp src/dfu.cpp src/dfuFileParser.cpp src/dirtyJtag.cpp @@ -97,6 +98,7 @@ set(OPENFPGALOADER_HEADERS src/anlogic.hpp src/anlogicBitParser.hpp src/anlogicCable.hpp + src/ch552_jtag.hpp src/cxxopts.hpp src/dfu.hpp src/dfuFileParser.hpp diff --git a/src/board.hpp b/src/board.hpp index eac7d6d..8828ba8 100644 --- a/src/board.hpp +++ b/src/board.hpp @@ -138,7 +138,7 @@ static std::map board_list = { DFU_BOARD("orangeCrab", "", "dfu", 0x1209, 0x5af0, 0), JTAG_BOARD("qmtechCycloneV", "5ce223", "", 0, 0, CABLE_DEFAULT), JTAG_BOARD("runber", "", "ft232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("tangnano", "", "ft2232", 0, 0, CABLE_DEFAULT), + JTAG_BOARD("tangnano", "", "ch552_jtag", 0, 0, CABLE_DEFAULT), JTAG_BOARD("tangnano4k", "", "ft2232", 0, 0, CABLE_DEFAULT), JTAG_BOARD("tec0117", "", "ft2232", 0, 0, CABLE_DEFAULT), JTAG_BITBANG_BOARD("ulx2s", "", "ft232RL", 0, 0, diff --git a/src/cable.hpp b/src/cable.hpp index 3d90c74..5be98b0 100644 --- a/src/cable.hpp +++ b/src/cable.hpp @@ -16,12 +16,13 @@ */ enum communication_type { MODE_ANLOGICCABLE = 0, /*! JTAG probe from Anlogic */ - MODE_FTDI_BITBANG = 1, /*! used with ft232RL/ft231x */ - MODE_FTDI_SERIAL = 2, /*! ft2232, ft232H */ - MODE_DIRTYJTAG = 3, /*! JTAG probe firmware for STM32F1 */ - MODE_USBBLASTER = 4, /*! JTAG probe firmware for USBBLASTER */ - MODE_CMSISDAP = 5, /*! CMSIS-DAP JTAG probe */ - MODE_DFU = 6, /*! DFU based probe */ + MODE_CH552_JTAG, /*! ch552_jtag firmware */ + MODE_FTDI_BITBANG, /*! used with ft232RL/ft231x */ + MODE_FTDI_SERIAL, /*! ft2232, ft232H */ + MODE_DIRTYJTAG, /*! JTAG probe firmware for STM32F1 */ + MODE_USBBLASTER, /*! JTAG probe firmware for USBBLASTER */ + MODE_CMSISDAP, /*! CMSIS-DAP JTAG probe */ + MODE_DFU, /*! DFU based probe */ }; typedef struct { @@ -35,6 +36,7 @@ static std::map cable_list = { {"anlogicCable", {MODE_ANLOGICCABLE, {}}}, {"bus_blaster", {MODE_FTDI_SERIAL, {0x0403, 0x6010, INTERFACE_A, 0x08, 0x1B, 0x08, 0x0B}}}, {"bus_blaster_b", {MODE_FTDI_SERIAL, {0x0403, 0x6010, INTERFACE_B, 0x08, 0x0B, 0x08, 0x0B}}}, + {"ch552_jtag", {MODE_FTDI_SERIAL, {0x0403, 0x6010, INTERFACE_A, 0x08, 0x0B, 0x08, 0x0B}}}, {"cmsisdap", {MODE_CMSISDAP, {0x0d28, 0x0204, 0, 0, 0, 0, 0 }}}, {"dfu", {MODE_DFU, {}}}, {"digilent", {MODE_FTDI_SERIAL, {0x0403, 0x6010, INTERFACE_A, 0xe8, 0xeb, 0x00, 0x60}}}, diff --git a/src/ch552_jtag.cpp b/src/ch552_jtag.cpp new file mode 100644 index 0000000..e60e996 --- /dev/null +++ b/src/ch552_jtag.cpp @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2020 Gwenhael Goavec-Merou + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "display.hpp" +#include "ch552_jtag.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 + +CH552_jtag::CH552_jtag(const FTDIpp_MPSSE::mpsse_bit_config &cable, + string dev, const string &serial, uint32_t clkHZ, bool verbose): + FTDIpp_MPSSE(cable, dev, serial, clkHZ, verbose), _ch552WA(false), + _write_mode(0), _read_mode(0), _to_read(0) +{ + init_internal(cable); +} + +CH552_jtag::~CH552_jtag() +{ + 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(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 CH552_jtag::init_internal(const FTDIpp_MPSSE::mpsse_bit_config &cable) +{ + display("iProduct : %s\n", _iproduct); + + if (!strncmp((const char *)_iproduct, "Sipeed-Debug", 12)) { + _ch552WA = 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); + + init(5, 0xfb, BITMODE_MPSSE); + config_edge(); + ftdi_set_event_char(_ftdi, 0, 0); + ftdi_set_error_char(_ftdi, 0, 0); + ftdi_set_latency_timer(_ftdi, 5); + ftdi_tciflush(_ftdi); + ftdi_tcoflush(_ftdi); + printInfo("fin"); +} + +int CH552_jtag::setClkFreq(uint32_t clkHZ) { + + int ret = FTDIpp_MPSSE::setClkFreq(clkHZ); + config_edge(); + return ret; +} + +void CH552_jtag::config_edge() +{ + /* at high (>15MHz) with digilent cable (arty) + * opposite edges must be used. + * Not required with classic FT2232 + */ + if (!strncmp((const char *)_iproduct, "Digilent USB Device", 19)) { + if (FTDIpp_MPSSE::getClkFreq() < 15000000) { + _write_mode = MPSSE_WRITE_NEG; + _read_mode = 0; + } else { + _write_mode = 0; + _read_mode = MPSSE_READ_NEG; + } + } else { + _write_mode = MPSSE_WRITE_NEG; + _read_mode = 0; + } +} + +int CH552_jtag::writeTMS(uint8_t *tms, uint32_t len, bool flush_buffer) +{ + (void) flush_buffer; + display("%s %d %d\n", __func__, len, (len/8)+1); + + if (len == 0) + return 0; + + uint32_t xfer = len; + uint32_t iter = (_buffer_size -8) / 4; + iter = (_buffer_size / 3); + uint32_t offset = 0, pos = 0; + flush_buffer = true; + + uint8_t buf[3]= {static_cast(MPSSE_WRITE_TMS | MPSSE_LSB | + MPSSE_BITMODE | _write_mode | + MPSSE_DO_READ | _read_mode), + 0, 0}; + while (xfer > 0) { + int bit_to_send = (xfer > 6) ? 6 : xfer; + buf[1] = bit_to_send-1; + buf[2] = 0x80; + + for (int i = 0; i < bit_to_send; i++, offset++) { + buf[2] |= + (((tms[offset >> 3] & (1 << (offset & 0x07))) ? 1 : 0) << i); + } + pos+=3; + _to_read++; + + mpsse_store(buf, 3); + if (pos >= iter) { + uint8_t tmp[_to_read]; + pos = 0; + if (-1 == mpsse_read(tmp, _to_read)) + printError("writeTMS: Fail to read/write"); + _to_read = 0; + } + xfer -= bit_to_send; + } + + if (flush_buffer) { + if (_to_read > 0) { + uint8_t tmp[_to_read]; + if (mpsse_read(tmp, _to_read) == -1) + printError("writeTMS: fail to flush"); + _to_read = 0; + } + if (_num > 0) + if (mpsse_write() == -1) + printError("writeTMS: fail to flush in write mode"); + } + + return len; +} + +int CH552_jtag::toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len) +{ + (void) tdi; + + int byteLen = (clk_len+7)/8; + uint8_t buf_tms[byteLen]; + + memset(buf_tms, (tms) ? 0xff : 0x00, byteLen); + return writeTMS(buf_tms, clk_len, false); +} + +int CH552_jtag::flush() +{ + int ret; + if (_to_read == 0) { + ret = mpsse_write(); + if (ret == -1) + printError("flush: fails to write"); + } else { + uint8_t tmp[_to_read]; + ret = mpsse_read(tmp, _to_read); + if (ret == -1) + printError("flush: fails to read/write"); + _to_read = 0; + } + return ret; +} + +int CH552_jtag::writeTDI(uint8_t *tdi, uint8_t *tdo, uint32_t len, bool last) +{ + bool rd_mode = (tdo) ? true : false; + /* 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 supress 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 - 7; // 2 byte for opcode and size 2 time + 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) | + ((rd_mode) ? (MPSSE_DO_READ | _read_mode) : 0)), + static_cast((xfer - 1) & 0xff), // low + static_cast((((xfer - 1) >> 8) & 0xff))}; // high + unsigned char def_cmd = tx_buf[0]; + unsigned char rd_cmd = def_cmd | (MPSSE_DO_READ | _read_mode); + /* read (and write) 1Byte */ + uint8_t oneshot_buf[3] = {rd_cmd, 0, 0}; + + if (_to_read != 0) { + uint8_t tmp_[_to_read]; + if (mpsse_read(tmp_, _to_read) == -1) + printError("writeTDI: fails to flush read"); + _to_read = 0; + } + + display("%s len : %d %d %d %d\n", __func__, len, real_len, nb_byte, + nb_bit); + + if (_num != 0 && ((nb_byte + _num + 3) > _buffer_size)) + if (mpsse_write() == -1) + printError("writeTDI: fails to flush write"); + + if ((nb_byte * 8) + nb_bit != real_len) { + printf("pas cool\n"); + throw std::exception(); + } + + /* first case: 1 full byte (and maybe up to 7bit) to send + * direct write (and read) + */ + if (nb_byte == 1) { + uint8_t tmp_; + mpsse_store(oneshot_buf, 3); + if (tdi) { + mpsse_store(tx_ptr, 1); + tx_ptr++; + } + if (mpsse_read(&tmp_, 1) == -1) + printError("writeTDI: fails to read/write with nb_byte == 1"); + if (rd_mode) { + *rx_ptr = tmp_; + rx_ptr++; + } + nb_byte--; + } + + while (nb_byte != 0) { + uint8_t tmp_; + int xfer_len = (nb_byte > xfer) ? xfer : nb_byte; + if (!rd_mode) { + xfer_len--; + } + if (xfer_len != 0) { + tx_buf[0] = def_cmd; + 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 (rd_mode) { + if (mpsse_read(rx_ptr, xfer_len) == -1) + printError("writeTDI: fails to read with nb_byte > 1"); + rx_ptr += xfer_len; + } else { + mpsse_store(oneshot_buf, 3); + if (tdi) { + mpsse_store(tx_ptr, 1); + tx_ptr++; + } + if (mpsse_read(&tmp_, 1) == -1) + printError("writeTDI: fails to read/write with nb_byte > 1"); + } + } else { + tx_buf[0] = rd_cmd; + tx_buf[1] = 0; + tx_buf[2] = 0; + mpsse_store(tx_buf, 3); + if (tdi) { + mpsse_store(tx_ptr, 1); + tx_ptr++; + } + if (mpsse_read(&tmp_, 1) == -1) + printError("writeTDI: fails to read/write with nb_byte == 0"); + if (rd_mode) { + *rx_ptr = tmp_; + rx_ptr++; + } + } + if (!rd_mode) + xfer_len++; + nb_byte -= xfer_len; + } + + unsigned char last_bit = (tdi) ? *tx_ptr : 0; + + /* next: serie of bit to send: inconditionnaly write AND read + */ + if (nb_bit != 0) { + display("%s read/write %d bit\n", __func__, nb_bit); + tx_buf[0] = rd_cmd | 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); + } + uint8_t tmp_; + if (mpsse_read(&tmp_, 1) == -1) + printError("writeTDI: fails to read/write serie of bits"); + if (rd_mode) { + *rx_ptr = tmp_; + /* 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); + } + } + + /* 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 | + (MPSSE_DO_READ | _read_mode); + 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); + uint8_t c; + if (mpsse_read(&c, 1) == -1) + printError("writeTDI: fails to read/write last transaction"); + if (rd_mode) { + /* in this case for 1 one it's always bit 7 */ + *rx_ptr |= ((c & 0x80) << (7 - nb_bit)); + } + } + + return 0; +} diff --git a/src/ch552_jtag.hpp b/src/ch552_jtag.hpp new file mode 100644 index 0000000..087d7a1 --- /dev/null +++ b/src/ch552_jtag.hpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2021 Gwenhael Goavec-Merou + */ + +#ifndef SRC_CH552_JTAG_HPP_ +#define SRC_CH552_JTAG_HPP_ +#include +#include +#include +#include + +#include "jtagInterface.hpp" +#include "ftdipp_mpsse.hpp" + +/*! + * \file ch552_jtag.hpp + * \class ch552_jtag + * \brief ch552_jtag firmware (ft2232C clone) implementation + * \author Gwenhael Goavec-Merou + */ + +class CH552_jtag : public JtagInterface, private FTDIpp_MPSSE { + public: + CH552_jtag(const FTDIpp_MPSSE::mpsse_bit_config &cable, std::string dev, + const std::string &serial, uint32_t clkHZ, bool verbose = false); + virtual ~CH552_jtag(); + + int setClkFreq(uint32_t clkHZ) override; + + uint32_t getClkFreq() override {return FTDIpp_MPSSE::getClkFreq();} + + /* TMS */ + int writeTMS(uint8_t *tms, uint32_t len, bool flush_buffer) override; + /* clock */ + int toggleClk(uint8_t tms, uint8_t tdi, uint32_t clk_len) override; + /* TDI */ + int writeTDI(uint8_t *tx, uint8_t *rx, uint32_t len, bool end) override; + + /*! + * \brief return internal buffer size (in byte). + * \return _buffer_size -3 for mpsse cmd + size, -1 for potential SEND_IMMEDIATE + */ + int get_buffer_size() override { return _buffer_size-3; } + + bool isFull() override { return false;} + + int flush() override; + + private: + void init_internal(const FTDIpp_MPSSE::mpsse_bit_config &cable); + /*! + * \brief configure read and write edge (pos or neg), with freq < 15MHz + * neg is used for write and pos to sample. with freq >= 15MHz + * pos is used for write and neg to sample + */ + void config_edge(); + bool _ch552WA; /* avoid errors with SiPeed tangNano */ + uint8_t _write_mode; /**< write edge configuration */ + uint8_t _read_mode; /**< read edge configuration */ + uint32_t _to_read; /*!< amount of byte to read */ +}; +#endif // SRC_CH552_JTAG_HPP_ diff --git a/src/jtag.cpp b/src/jtag.cpp index 5435f87..787ab40 100644 --- a/src/jtag.cpp +++ b/src/jtag.cpp @@ -13,6 +13,7 @@ #include #include "anlogicCable.hpp" +#include "ch552_jtag.hpp" #include "display.hpp" #include "jtag.hpp" #include "ftdipp_mpsse.hpp" @@ -91,6 +92,9 @@ void Jtag::init_internal(cable_t &cable, const string &dev, const string &serial case MODE_FTDI_SERIAL: _jtag = new FtdiJtagMPSSE(cable.config, dev, serial, clkHZ, _verbose); break; + case MODE_CH552_JTAG: + _jtag = new CH552_jtag(cable.config, dev, serial, clkHZ, _verbose); + break; case MODE_DIRTYJTAG: _jtag = new DirtyJtag(clkHZ, _verbose); break;