// SPDX-License-Identifier: Apache-2.0 /* * Copyright (C) 2024 Gwenhael Goavec-Merou */ #include #include #include #include #include #include #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 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(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(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(((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(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 */ uint32_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((len >> 0) & 0xff), static_cast((len >> 8) & 0xff))) return -1; len += 1; } /* flush before return */ if (_xfer_pos != 0) { if (!xfer(nullptr, 0)) return -1; } return static_cast(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(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(60e6 / freqHz); const int real_freq = static_cast(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(div - ((freqHz < 240e3) ? 256 : 0)); if (!xfer(nullptr, 0, 1000)) return -1; _clkHZ = real_freq; return static_cast(_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); } }