This is an automated email from Gerrit.

"Richard Pasek <[email protected]>" just uploaded a new patch set to Gerrit, 
which you can find at https://review.openocd.org/c/openocd/+/9554

-- gerrit

commit 283569ee737f4110d7c48af9640f091850d06669
Author: Richard Pasek <[email protected]>
Date:   Mon Mar 30 20:28:33 2026 -0400

    Add USB3813 SWD adapter support
    
    The Microchip USB3813 is a very small (2.5mmx2.9mm) chip scale package
    USB hub that also features SPI passthrough and a few GPIOs. Following
    the same technique as linuxspidev.c, we can use this SPI port to emulate
    SWD. Because of its small size it can be embedded into devices, allowing
    for debug of sealed device and passing through an SOC's USB port while
    only exposing a single physical USB port to the outside world.
    
    TEST:
      Connects successfully to an RT595 using a 3.3v to 1.8v level shifter
    
    Change-Id: I7f17d32b282bc606413db9f2589319776607dcec
    Signed-off-by: Richard Pasek <[email protected]>

diff --git a/configure.ac b/configure.ac
index 39f5e074ed..84cf976497 100644
--- a/configure.ac
+++ b/configure.ac
@@ -187,6 +187,10 @@ m4_define([PARALLEL_PORT_ADAPTER],
 
 m4_define([LINUXSPIDEV_ADAPTER],
        [[[linuxspidev], [Linux spidev driver], [LINUXSPIDEV]]])
+
+m4_define([USB3813SPI_ADAPTER],
+       [[[usb3813spi], [USB3813 hub SPI driver], [USB3813SPI]]])
+
 m4_define([VDEBUG_ADAPTER],
        [[[vdebug], [Cadence Virtual Debug Interface], [VDEBUG]]])
 
@@ -288,6 +292,7 @@ AC_ARG_ADAPTERS([
   SYSFSGPIO_ADAPTER,
   REMOTE_BITBANG_ADAPTER,
   LINUXSPIDEV_ADAPTER,
+  USB3813SPI_ADAPTER,
   SERIAL_PORT_ADAPTERS,
   DUMMY_ADAPTER,
   VDEBUG_ADAPTER,
@@ -611,6 +616,8 @@ PROCESS_ADAPTERS([SERIAL_PORT_ADAPTERS], 
["x$can_build_buspirate" = "xyes"],
 PROCESS_ADAPTERS([PARALLEL_PORT_ADAPTER], [true], [unused])
 PROCESS_ADAPTERS([LINUXSPIDEV_ADAPTER], ["x$is_linux" = "xyes" -a 
"x$ac_cv_header_linux_spi_spidev_h" = "xyes"],
                                         [Linux spidev])
+PROCESS_ADAPTERS([USB3813SPI_ADAPTER], ["x$use_libusb1" = "xyes"],
+                                        [libusb-1.x])
 PROCESS_ADAPTERS([VDEBUG_ADAPTER], [true], [unused])
 PROCESS_ADAPTERS([JTAG_DPI_ADAPTER], [true], [unused])
 PROCESS_ADAPTERS([JTAG_VPI_ADAPTER], [true], [unused])
@@ -815,6 +822,7 @@ m4_foreach([adapter_driver], [USB1_ADAPTERS,
        SERIAL_PORT_ADAPTERS,
        PARALLEL_PORT_ADAPTER,
        LINUXSPIDEV_ADAPTER,
+       USB3813SPI_ADAPTER,
        VDEBUG_ADAPTER,
        JTAG_DPI_ADAPTER,
        JTAG_VPI_ADAPTER,
diff --git a/doc/openocd.texi b/doc/openocd.texi
index a21a3e4bda..ec195bf0e1 100644
--- a/doc/openocd.texi
+++ b/doc/openocd.texi
@@ -622,6 +622,9 @@ emulation model of target hardware.
 @item @b{linuxspidev}
 @* A SPI based SWD driver using Linux SPI devices.
 
+@item @b{usb3813spi}
+@* A SPI based SWD driver using Microchip USB3813 USB hub SPI passthrough 
feature.
+
 @item @b{linuxgpiod}
 @* A bitbang JTAG driver using Linux GPIO through library libgpiod.
 
@@ -3634,6 +3637,38 @@ Try installing 10K resistors on SWDIO and SWDCLK to 
ground to stop this.
 @end itemize
 @end deffn
 
+@deffn {Interface Driver} {usb3813spi}
+This driver supports SPI-based SWD through the Microchip USB3813 Hub Feature
+Controller. It uses vendor-specific USB control transfers to bridge SPI
+traffic. This driver's method for operation is nearly identical to linuxspidev.
+Please see linuxspidev's documentation.
+
+SWD (SPI) frequency cannot be modified through code and can only be modified
+through strap resistors. The only supported frequencies are 30 and 60 MHz.
+
+Since SWD frequencys are so high, you should replace the 1K resistor suggested
+in linuxspidev with a 100 ohm resistor.
+
+@deffn {Config Command} {usb3813spi vid_pid} vid pid
+Specifies the vendor ID and product ID of the USB3813 Hub Feature Controller
+(default 0x0424:0x2530).
+@end deffn
+
+@deffn {Config Command} {usb3813spi hub_vid_pid} vid pid
+Specifies the vendor ID and product ID of the Hub itself (default
+0x0424:0x3813). This is used to automatically enable the Hub Feature Controller
+if it is disabled.
+@end deffn
+
+@deffn {Config Command} {usb3813spi mode} mode
+Specifies the SPI mode (0 or 3, default 3).
+@end deffn
+
+@deffn {Config Command} {usb3813spi queue_entries} entries
+Specifies the maximum number of SWD transactions to queue (default 32, max 32).
+@end deffn
+@end deffn
+
 @deffn {Interface Driver} {linuxgpiod}
 Linux provides userspace access to GPIO through libgpiod since Linux kernel
 version v4.6. The driver emulates either JTAG or SWD transport through
diff --git a/src/jtag/drivers/Makefile.am b/src/jtag/drivers/Makefile.am
index 5216e5d774..51b7ffe58e 100644
--- a/src/jtag/drivers/Makefile.am
+++ b/src/jtag/drivers/Makefile.am
@@ -177,6 +177,9 @@ endif
 if LINUXSPIDEV
 DRIVERFILES += %D%/linuxspidev.c
 endif
+if USB3813SPI
+DRIVERFILES += %D%/usb3813spi.c
+endif
 if XLNX_XVC
 DRIVERFILES += %D%/xlnx-xvc.c
 endif
diff --git a/src/jtag/drivers/usb3813spi.c b/src/jtag/drivers/usb3813spi.c
new file mode 100644
index 0000000000..fcdb14b057
--- /dev/null
+++ b/src/jtag/drivers/usb3813spi.c
@@ -0,0 +1,856 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/* Copyright (C) 2020 by Lup Yuen Lee <[email protected]>
+ * Copyright (C) 2026 by Richard Pasek <[email protected]>
+ */
+
+/**
+ * @file
+ * Implementation of SWD protocol with a Microchip USB3813 hub SPI interface.
+ *
+ * The Microchip USB3813 is a very small (2.5mmx2.9mm) chip scale package USB
+ * hub that also features SPI passthrough and a few GPIOs. Following the same
+ * technique as linuxspidev.c, we can use this SPI port to emulate SWD. Because
+ * of its small size it can be embedded into devices, allowing for debug of
+ * sealed device and passing through an SOC's USB port while only exposing a
+ * single physical USB port to the outside world.
+ *
+ * The Microchip USB3813 is originally an SMSC part. It's very poorly
+ * documented, seemingly intentionally. Original SMSC documentation contains
+ * more information than Microchip documentation. Most of the SMSC
+ * documentation is no longer online.
+ *
+ * Here are the documents and code that was used to create this driver:
+ * 1. USB3813: USB 2.0 Hi-Speed 3-Port Hub Controller Optimized for Portable 
Applications
+ *             DS00001715D: 
https://ww1.microchip.com/downloads/en/DeviceDoc/00001715C.pdf
+ * 2. AN1971: USB to SPI Bridging with Microchip USB 2.0 Hubs
+ *             DS00001971B: 
https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ApplicationNotes/ApplicationNotes/00001971B.pdf
+ * 3. AN 26.18: Configuration Options for the USB253x / USB3x13 / USB46x4
+ *             DS00001801C: 
http://ww1.microchip.com/downloads/en/AppNotes/00001801C.pdf
+ * 4. AN2083: USB Commands for the USB253x/3x13/46x4 Hub Feature Controller
+ *             DS00002083A: 
https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ApplicationNotes/ApplicationNotes/00002083A.pdf
+ * 5. SEC1110/SEC1210 Smart Card Bridge to USB, SPI, and UART Interfaces.
+ *             (DS01561A) https://docs.rs-online.com/7bf0/0900766b8135d288.pdf
+ *             This document contains information about the SPI controller that
+ *             Microchip website documentation lacks.
+ * 6. SEC2410/SEC4410: HS Endpoint Processor with USB 2.0, Smart Card,& FMC 
for Secure Token & Storage
+ *             Revision 1.0 (03-07-13): 
https://ww1.microchip.com/downloads/en/DeviceDoc/sec2410_4410.pdf
+ * 7. USB4640/USB4640i: High-Speed Inter-Chip (HSIC) USB 2.0 Hub and Flash 
Media Controller
+ *     DS00001922B: 
https://ww1.microchip.com/downloads/en/DeviceDoc/USB4640-USB4640i-High-Speed-Inter-Chip-(HSIC)-USB%202.0-Hub-and-Flash-Media-Controller-Data-Sheet.pdf
+ * 8. USB-Hub-Linux-Examples: Linux example code that accompanies MCHP USB 
Smart hubs
+ *             https://github.com/MicrochipTech/USB-Hub-Linux-Examples
+ *
+ * This driver will likely work with the following Microchip USB hubs with no
+ * modification (testing was only performed on USB3813):
+ *     USB253x / USB3x13 / USB46x4
+ *
+ * Other Microchip hubs will likely need modification to REG_SPI_BASE.
+ *
+ * The SPI bus appears to only be able operate at 30 or 60 MHz and is 
selectable
+ * exclusively with strap resistors. The SEC1210 documentation discusses a
+ * divisor that would provide a lot of flexibility but it's unknown if and 
where
+ * it's present in USB3813.
+ */
+
+/* Uncomment to log SPI exchanges (very verbose, slows things down a lot)
+ *
+ * A quick note on interpreting SPI exchange messages:
+ *
+ * This implementation works by performing SPI exchanges with MOSI and MISO
+ * tied together with a 100 ohm resistor. This combined signal becomes SWDIO.
+ *
+ * Since we are performing SPI exchanges, (reading and writing at the same
+ * time) this means when the target isn't driving SWDIO, what is written by
+ * the host should also be read by the host.
+ *
+ * On SWD writes:
+ *   The TX and RX data should match except for the ACK bits from the target
+ *     swd write reg exchange: len=6
+ *      tx_buf=C5 02 40 00 02 2C
+ *      rx_buf=C5 42 40 00 02 2C
+ *                ^
+ *                |
+ *          ACK from target
+ *
+ * On SWD reads:
+ *   Only the command byte should match
+ *     swd read reg exchange: len=6
+ *      tx_buf=B1 00 00 00 00 00
+ *      rx_buf=B1 40 20 00 00 F8
+ *             ^^
+ *             ||
+ *     Command packet from host
+ *
+ */
+// #define LOG_SPI_EXCHANGE
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include "libusb_helper.h"
+#include <jtag/adapter.h>
+#include <jtag/swd.h>
+#include <jtag/interface.h>
+#include <jtag/commands.h>
+#include <helper/binarybuffer.h>
+
+// Number of bits per SPI exchange
+#define SPI_BITS 8
+
+// Time in uS after the last bit of transfer before deselecting the device
+#define SPI_DESELECT_DELAY 0
+
+/* Maximum number of SWD transactions to queue together in a SPI exchange.
+   USB3813 can only transmit 256 bytes at a time. Worst case we should be able
+   to fit 32 transactions */
+#define MAX_QUEUE_ENTRIES 32
+
+#define USB_TIMEOUT_MS 1000
+
+#define CMD_BITS 8
+#define TURN_BITS 1
+#define ACK_BITS 3
+#define DATA_BITS 32
+#define PARITY_BITS 1
+
+#define SWD_WR_BITS (CMD_BITS + TURN_BITS + ACK_BITS + TURN_BITS + DATA_BITS + 
PARITY_BITS)
+#define SWD_RD_BITS (CMD_BITS + TURN_BITS + ACK_BITS + DATA_BITS + PARITY_BITS 
+ TURN_BITS)
+#define SWD_OP_BITS (MAX(SWD_WR_BITS, SWD_RD_BITS))
+#define SWD_OP_BYTES (DIV_ROUND_UP(SWD_OP_BITS, SPI_BITS))
+
+#define AP_DELAY_CLOCKS 8
+#define AP_DELAY_BYTES (DIV_ROUND_UP(AP_DELAY_CLOCKS, SPI_BITS))
+
+#define END_IDLE_CLOCKS 8
+#define END_IDLE_BYTES (DIV_ROUND_UP(END_IDLE_CLOCKS, SPI_BITS))
+
+// USB bridging commands
+#define CMD_SPI_ENTER_PASSTHRU 0x60
+#define CMD_SPI_WRITE          0x61
+#define CMD_SPI_EXIT_PASSTHRU  0x62
+#define CMD_REGISTER_WRITE     0x03
+#define CMD_REGISTER_READ      0x04
+#define REG_SPI_CONFIG         0x4A04
+#define REG_SPI_DATA              0x4A10
+#define CMD_HUB_WRITE_MEMORY   0x02
+
+// USB request types
+#define VENDOR_DEVICE_OUT              (LIBUSB_REQUEST_TYPE_VENDOR | 
LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT)
+#define VENDOR_DEVICE_IN               (LIBUSB_REQUEST_TYPE_VENDOR | 
LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_IN)
+#define VENDOR_INTERFACE_OUT   (LIBUSB_REQUEST_TYPE_VENDOR | 
LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT)
+#define VENDOR_INTERFACE_IN            (LIBUSB_REQUEST_TYPE_VENDOR | 
LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN)
+
+/* The following SPI base address was found in the USB4640 datasheet (doc 7)
+ * which shares a similar design to the USB3813. */
+#define REG_SPI_BASE            0x2400
+
+/* The following registers were extracted from the SEC2410 datasheet (doc 6).
+ * This part appears to have the same SPI hardware block as the USB3813. This
+ * was verified by observing USB3813 initial values, modifying state, observing
+ * the behavior and comparing the results to the SEC2410 datasheet.
+ *
+ * Unlike SEC2410, a register to control SPI drive strength was not found. */
+
+/* Table 12.2 SPI Mode Control Register (SPI_CTL: 0X0000 - RESET=0X02) */
+#define REG_SPI_CTL             (REG_SPI_BASE + 0x0000)
+/* Bit 7: SPI_SPEED (R) - This bit reflects the strap option of the SPI_SPEED
+ * option during reset. This is to allow the firmware to know what speed it
+ * is operating at. 0: 30 Mhz, 1: 60 Mhz */
+#define SPI_CTL_SPI_SPEED       (1 << 7)
+/* Bit 4: FORCE_CE (R/W) - When this bit is set, it forces the SPI chip enable
+ * low. This bit should only be set when using the SPI command buffer. It
+ * should never be set when doing direct accesses from the AHB bus. */
+#define SPI_CTL_FORCE_CE        (1 << 4)
+/* Bit 3: DUAL_OUT_EN (R/W) - 0:Dual output disabled for fast reads,
+ * 1:Dual output enabled for fast reads. */
+#define SPI_CTL_DUAL_OUT_EN     (1 << 3)
+/* Bit 2: MODE_SEL (R/W) - This set the SPI clock mode. 0: Mode 0, 1: Mode 3 */
+#define SPI_CTL_MODE_SEL        (1 << 2)
+/* Bit 1: CACHE_EN (R/W) - Enable the SPI cache */
+#define SPI_CTL_CACHE_EN        (1 << 1)
+/* Bit 0: GO (R/W) - This is a self clearing bit. Setting this bit will cause
+ * the SPI transaction to initiate. */
+#define SPI_CTL_GO              (1 << 0)
+
+/* Table 12.3 SPI Command Length Register (SPI_CMD_LEN: 0X0001 - RESET=0X00)
+ * Bits 7:0: CMD_LEN[7:0] (R/W) - This is the length of the SPI transaction
+ * length for firmware initiated transactions. */
+#define REG_SPI_CMD_LEN         (REG_SPI_BASE + 0x0001)
+
+/* Table 12.4 SPI Trace Fifo opcode (SPI_TF_OPCODE: 0X0002 - RESET=0X00)
+ * Bits 7:0: TF_OPCODE (R/W) - This is the opcode used when the processor does
+ * a write to 0xBFFE or 0xBFFF (unconfirmed on USB3813). Use the value of 0xDB
+ */
+#define REG_SPI_TF_OPCODE       (REG_SPI_BASE + 0x0002)
+
+/* Table 12.5 Fast Read Opcode (SPI_FR_OPCODE: 0X0003 - RESET=0X0B)
+ * Bits 7:0: FR_OPCODE (R/W) - This is the opcode used when the processor does
+ * a fast read. 0x0B: Single output read, 0x3B: Dual output read */
+#define REG_SPI_FR_OPCODE       (REG_SPI_BASE + 0x0003)
+
+/* Table 12.6 SPI Command Buffer (SPI_CMD_BUF: 0X0008~0X000F - RESET=0X00)
+ * Bytes 7:0: SPI_CMD_BUF[0:7] (R/W) - This buffer is used by processor to
+ * store outgoing SPI commands. See behavioral description. Note: First byte
+ * to go out is SPI_CMD_BUF[0] at offset location 0x0008. */
+#define REG_SPI_CMD_BUF         (REG_SPI_BASE + 0x0008)
+
+/* Table 12.7 SPI Response Buffer (SPI_RSP_BUF: 0X0010~0X0017 - RESET=0X00)
+ * Bytes 7:0: SPI_RSP_BUF[0:7] (R/W) - This buffer is used by processor to
+ * store incoming SPI responses. See behavioral description. Note: First byte
+ * to be written is SPI_RSP_BUF[0] at offset location 0x0010 */
+#define REG_SPI_RSP_BUF         (REG_SPI_BASE + 0x0010)
+
+// libusb device handle
+static struct libusb_device_handle *hfc_hdl;
+
+// SPI Configuration
+static uint16_t spi_vid = 0x0424;
+static uint16_t spi_pid = 0x2530;
+static uint16_t hub_vid = 0x0424;
+static uint16_t hub_pid = 0x3813;
+static uint32_t spi_mode = 3;
+
+struct queue_info {
+       unsigned int buf_idx;
+       uint32_t *rx_ptr;
+};
+
+static int queue_retval;
+static unsigned int max_queue_entries;
+static unsigned int queue_fill;
+static unsigned int queue_buf_fill;
+static unsigned int queue_buf_size;
+static struct queue_info *queue_infos;
+static uint8_t *queue_tx_buf;
+static uint8_t *queue_rx_buf;
+static uint8_t *tx_flip_buf;
+
+static int usb3813spi_swd_switch_seq(enum swd_special_seq seq);
+static void usb3813spi_swd_write_reg(uint8_t cmd, uint32_t value, uint32_t 
ap_delay_clk);
+
+static void spi_exchange(const uint8_t *tx_data, uint8_t *rx_data, unsigned 
int len)
+{
+#ifdef LOG_SPI_EXCHANGE
+       LOG_OUTPUT("exchange: len=%u\n", len);
+#endif // LOG_SPI_EXCHANGE
+
+       if (len == 0) {
+               LOG_DEBUG("exchange with no length");
+               return;
+       }
+
+       if (!tx_data && !rx_data) {
+               LOG_DEBUG("exchange with no valid tx or rx pointers");
+               return;
+       }
+
+       if (len > queue_buf_size) {
+               LOG_ERROR("exchange too large len=%u ", len);
+               return;
+       }
+
+       if (tx_data) {
+               // Reverse LSB to MSB
+               for (unsigned int i = 0; i < len; i++)
+                       tx_flip_buf[i] = flip_u32(tx_data[i], 8);
+
+#ifdef LOG_SPI_EXCHANGE
+               if (len != 0) {
+                       LOG_OUTPUT(" tx_buf=");
+                       for (unsigned int i = 0; i < len; i++)
+                               LOG_OUTPUT("%.2" PRIx8 " ", tx_flip_buf[i]);
+               }
+               LOG_OUTPUT("\n");
+#endif // LOG_SPI_EXCHANGE
+       } else {
+               memset(tx_flip_buf, 0, len);
+       }
+
+       // Transmit the MSB buffer to USB device using SPI write command.
+       int ret = jtag_libusb_control_transfer(hfc_hdl,
+               VENDOR_INTERFACE_OUT,
+               CMD_SPI_WRITE,
+               len,
+               0,
+               (char *)tx_flip_buf,
+               len,
+               USB_TIMEOUT_MS,
+               NULL);
+
+       if (ret != ERROR_OK) {
+               LOG_ERROR("USB SPI write failed");
+               return;
+       }
+
+       if (rx_data) {
+               // Read back the received data from the hub's internal register.
+               ret = jtag_libusb_control_transfer(hfc_hdl,
+                       VENDOR_INTERFACE_IN,
+                       CMD_REGISTER_READ,
+                       REG_SPI_DATA,
+                       0,
+                       (char *)rx_data,
+                       len,
+                       USB_TIMEOUT_MS,
+                       NULL);
+
+               if (ret != ERROR_OK) {
+                       LOG_ERROR("USB SPI register read failed");
+                       return;
+               }
+
+#ifdef LOG_SPI_EXCHANGE
+               if (len != 0) {
+                       LOG_OUTPUT(" rx_buf=");
+                       for (unsigned int i = 0; i < len; i++)
+                               LOG_OUTPUT("%.2" PRIx8 " ", rx_data[i]);
+               }
+               LOG_OUTPUT("\n");
+#endif // LOG_SPI_EXCHANGE
+
+               // Reverse MSB to LSB
+               for (unsigned int i = 0; i < len; i++)
+                       rx_data[i] = flip_u32(rx_data[i], 8);
+       }
+}
+
+static void usb3813spi_free_queue(void)
+{
+       max_queue_entries = 0;
+       queue_buf_size = 0;
+
+       free(queue_infos);
+       queue_infos = NULL;
+
+       free(queue_tx_buf);
+       queue_tx_buf = NULL;
+
+       free(queue_rx_buf);
+       queue_rx_buf = NULL;
+
+       free(tx_flip_buf);
+       tx_flip_buf = NULL;
+}
+
+static void usb3813spi_clear_queue(void)
+{
+       queue_fill = 0;
+       queue_buf_fill = 0;
+
+       memset(queue_infos, 0, sizeof(struct queue_info) * max_queue_entries);
+       memset(queue_tx_buf, 0, queue_buf_size);
+       memset(queue_rx_buf, 0, queue_buf_size);
+       memset(tx_flip_buf, 0, queue_buf_size);
+}
+
+static int usb3813spi_alloc_queue(unsigned int new_queue_entries)
+{
+       if (queue_fill || queue_buf_fill) {
+               LOG_ERROR("Can't realloc queue when queue is in use");
+               return ERROR_FAIL;
+       }
+
+       unsigned int new_queue_buf_size =
+               (new_queue_entries * (SWD_OP_BYTES + AP_DELAY_BYTES)) + 
END_IDLE_BYTES;
+
+       queue_infos = realloc(queue_infos, sizeof(struct queue_info) * 
new_queue_entries);
+       if (!queue_infos)
+               goto realloc_fail;
+
+       queue_tx_buf = realloc(queue_tx_buf, new_queue_buf_size);
+       if (!queue_tx_buf)
+               goto realloc_fail;
+
+       queue_rx_buf = realloc(queue_rx_buf, new_queue_buf_size);
+       if (!queue_rx_buf)
+               goto realloc_fail;
+
+       tx_flip_buf = realloc(tx_flip_buf, new_queue_buf_size);
+       if (!tx_flip_buf)
+               goto realloc_fail;
+
+       max_queue_entries = new_queue_entries;
+       queue_buf_size = new_queue_buf_size;
+
+       usb3813spi_clear_queue();
+
+       LOG_DEBUG("Set queue entries to %u (buffers %u bytes)", 
max_queue_entries, queue_buf_size);
+
+       return ERROR_OK;
+
+realloc_fail:
+       usb3813spi_free_queue();
+
+       LOG_ERROR("Couldn't allocate queue. Out of memory.");
+       return ERROR_FAIL;
+}
+
+static int usb3813spi_init(void)
+{
+       LOG_INFO("USB3813 SPI SWD driver");
+
+       if (hfc_hdl)
+               return ERROR_OK;
+
+       uint16_t vids[] = { spi_vid, 0 };
+       uint16_t pids[] = { spi_pid, 0 };
+
+       // Open USB device.
+       if (jtag_libusb_open(vids, pids, NULL, &hfc_hdl, NULL) != ERROR_OK) {
+               LOG_INFO("HFC (Hub Feature Controller) not found at %04" PRIx16 
":%04" PRIx16 ", checking for hub...",
+                            spi_vid, spi_pid);
+
+               uint16_t hub_vids[] = { hub_vid, 0 };
+               uint16_t hub_pids[] = { hub_pid, 0 };
+               struct libusb_device_handle *hub_hdl = NULL;
+               uint16_t val;
+
+               if (jtag_libusb_open(hub_vids, hub_pids, NULL, &hub_hdl, NULL) 
!= ERROR_OK) {
+                       LOG_ERROR("Failed to open USB hub %04" PRIx16 ":%04" 
PRIx16, hub_vid, hub_pid);
+                               return ERROR_JTAG_INIT_FAILED;
+               }
+               LOG_INFO("Opened USB hub %04" PRIx16 ":%04" PRIx16 ", enabling 
HFC...", hub_vid, hub_pid);
+
+               if(libusb_kernel_driver_active(hub_hdl, 0) == 1)
+               {
+                       if(libusb_detach_kernel_driver(hub_hdl, 0) != 
LIBUSB_SUCCESS)
+                       {
+                               LOG_ERROR("Failed to detach hub kernel driver. 
USB device may not respond");
+                       }
+               }
+
+               if(libusb_claim_interface(hub_hdl, 0) != LIBUSB_SUCCESS)
+               {
+                       LOG_ERROR("Failed to claim hub interface");
+               }
+
+               /* Unknown what this value means. Found in
+                  USB-Hub-Linux-Examples/USB Hub Feature 
Examples/USB4715/Register_Access_via_USB/MPLABConnect/MchpUSBInterface.cpp
+                */
+               val = 0x0001;
+               if (jtag_libusb_control_transfer(hub_hdl,
+                                                                               
        VENDOR_DEVICE_OUT,
+                                                                               
        CMD_HUB_WRITE_MEMORY,
+                                                                               
        0x0,
+                                                                               
        0x0,
+                                                                               
        (char *)&val,
+                                                                               
        2,
+                                                                               
        USB_TIMEOUT_MS,
+                                                                               
        NULL) != ERROR_OK) {
+                       LOG_ERROR("Failed to enable HFC");
+               }
+
+               /* Unknown what this value means. Found in
+                  USB-Hub-Linux-Examples/USB Hub Feature 
Examples/USB4715/Register_Access_via_USB/MPLABConnect/MchpUSBInterface.cpp
+                */
+               val = 0x0201;
+               if (jtag_libusb_control_transfer(hub_hdl,
+                                                                               
        VENDOR_DEVICE_OUT,
+                                                                               
        CMD_HUB_WRITE_MEMORY,
+                                                                               
        0x0,
+                                                                               
        0x0,
+                                                                               
        (char *)&val,
+                                                                               
        2,
+                                                                               
        USB_TIMEOUT_MS,
+                                                                               
        NULL) != ERROR_OK) {
+                       LOG_ERROR("Failed to enable HFC");
+               }
+
+               jtag_libusb_close(hub_hdl);
+
+               LOG_INFO("Waiting for hub to re-enumerate...");
+
+               jtag_sleep(5 * 1000 * 1000); // 5 seconds
+
+               // Try again to open the HFC device
+               if (jtag_libusb_open(vids, pids, NULL, &hfc_hdl, NULL) != 
ERROR_OK) {
+                       LOG_ERROR("Failed to open HFC device %04" PRIx16 ":%04" 
PRIx16, spi_vid, spi_pid);
+                       return ERROR_JTAG_INIT_FAILED;
+               }
+       }
+
+       // Enable SPI pass-through interface.
+       if (jtag_libusb_control_transfer(hfc_hdl,
+                                                                        
VENDOR_INTERFACE_OUT,
+                                                                        
CMD_SPI_ENTER_PASSTHRU,
+                                                                        0,
+                                                                        0,
+                                                                        NULL,
+                                                                        0,
+                                                                        
USB_TIMEOUT_MS,
+                                                                        NULL) 
!= ERROR_OK) {
+               LOG_ERROR("Failed to enable USB SPI bridge");
+               jtag_libusb_close(hfc_hdl);
+               hfc_hdl = NULL;
+               return ERROR_JTAG_INIT_FAILED;
+       }
+
+       LOG_INFO("Opened USB device %04" PRIx16 ":%04" PRIx16 " and enabled SPI 
bridge", spi_vid, spi_pid);
+
+       // Configure SPI mode
+       uint8_t spi_ctl = (spi_mode == 3) ? SPI_CTL_MODE_SEL : 0;
+       if (jtag_libusb_control_transfer(hfc_hdl,
+                                                                        
VENDOR_INTERFACE_OUT,
+                                                                        
CMD_REGISTER_WRITE,
+                                                                        
REG_SPI_CTL,
+                                                                        0,
+                                                                        (char 
*)&spi_ctl,
+                                                                        1,
+                                                                        
USB_TIMEOUT_MS,
+                                                                        NULL) 
!= ERROR_OK) {
+               LOG_ERROR("Failed to configure USB SPI bridge");
+               jtag_libusb_close(hfc_hdl);
+               hfc_hdl = NULL;
+               return ERROR_JTAG_INIT_FAILED;
+       }
+
+       // Read back SPI_CTL
+       if (jtag_libusb_control_transfer(hfc_hdl,
+                                                                        
VENDOR_INTERFACE_IN,
+                                                                        
CMD_REGISTER_READ,
+                                                                        
REG_SPI_CTL,
+                                                                        0,
+                                                                        (char 
*)&spi_ctl,
+                                                                        1,
+                                                                        
USB_TIMEOUT_MS,
+                                                                        NULL) 
!= ERROR_OK) {
+               LOG_ERROR("Failed to configure USB SPI bridge");
+               jtag_libusb_close(hfc_hdl);
+               hfc_hdl = NULL;
+               return ERROR_JTAG_INIT_FAILED;
+       }
+       LOG_INFO("clock speed %d kHz (set with strap resistor), mode %d",
+                        (spi_ctl & SPI_CTL_SPI_SPEED) ? 60000 : 30000,
+                        (spi_ctl & SPI_CTL_MODE_SEL) ? 3 : 1);
+
+       if (max_queue_entries == 0) {
+               int ret = usb3813spi_alloc_queue(MAX_QUEUE_ENTRIES);
+               if (ret != ERROR_OK)
+                       return ERROR_JTAG_INIT_FAILED;
+       }
+
+       return ERROR_OK;
+}
+
+static int usb3813spi_quit(void)
+{
+       usb3813spi_free_queue();
+
+       if (!hfc_hdl)
+               return ERROR_OK;
+
+       // Disable SPI pass-through interface.
+       jtag_libusb_control_transfer(hfc_hdl,
+                                                                
VENDOR_INTERFACE_OUT,
+                                                                
CMD_SPI_EXIT_PASSTHRU,
+                                                                0,
+                                                                0,
+                                                                NULL,
+                                                                0,
+                                                                USB_TIMEOUT_MS,
+                                                                NULL);
+
+       jtag_libusb_close(hfc_hdl);
+       hfc_hdl = NULL;
+
+       return ERROR_OK;
+}
+
+static int usb3813spi_swd_init(void)
+{
+       LOG_DEBUG("usb3813spi_swd_init");
+       return ERROR_OK;
+}
+
+static int usb3813spi_swd_execute_queue(unsigned int end_idle_bytes)
+{
+       LOG_DEBUG_IO("Executing %u queued transactions", queue_fill);
+
+       if (queue_retval != ERROR_OK) {
+               LOG_DEBUG_IO("Skipping due to previous errors: %d", 
queue_retval);
+               goto skip;
+       }
+
+       /* A transaction must be followed by another transaction or at least 8 
idle
+        * cycles to ensure that data is clocked through the AP. Since the tx
+        * buffer is zeroed after each queue run, every byte added to the buffer
+        * fill will add on an additional 8 idle cycles.
+        */
+       queue_buf_fill += end_idle_bytes;
+
+       spi_exchange(queue_tx_buf, queue_rx_buf, queue_buf_fill);
+
+       for (unsigned int queue_idx = 0; queue_idx < queue_fill; queue_idx++) {
+               unsigned int buf_idx = queue_infos[queue_idx].buf_idx;
+               uint8_t *tx_ptr = &queue_tx_buf[buf_idx];
+               uint8_t *rx_ptr = &queue_rx_buf[buf_idx];
+               uint8_t cmd = buf_get_u32(tx_ptr, 0, CMD_BITS);
+               bool read = cmd & SWD_CMD_RNW ? true : false;
+               int ack = buf_get_u32(rx_ptr, CMD_BITS + TURN_BITS, ACK_BITS);
+               uint32_t data = read ?
+                       buf_get_u32(rx_ptr, CMD_BITS + TURN_BITS + ACK_BITS, 
DATA_BITS) :
+                       buf_get_u32(tx_ptr, CMD_BITS + TURN_BITS + ACK_BITS + 
TURN_BITS, DATA_BITS);
+
+               // Devices do not reply to DP_TARGETSEL write cmd, ignore 
received ack
+               bool check_ack = swd_cmd_returns_ack(cmd);
+
+               LOG_CUSTOM_LEVEL((check_ack && ack != SWD_ACK_OK) ? 
LOG_LVL_DEBUG : LOG_LVL_DEBUG_IO,
+                               "%s%s %s %s reg %X = %08" PRIx32,
+                               check_ack ? "" : "ack ignored ",
+                               ack == SWD_ACK_OK ? "OK" :
+                               ack == SWD_ACK_WAIT ? "WAIT" :
+                               ack == SWD_ACK_FAULT ? "FAULT" : "JUNK",
+                               cmd & SWD_CMD_APNDP ? "AP" : "DP",
+                               read ? "read" : "write",
+                               (cmd & SWD_CMD_A32) >> 1,
+                               data);
+
+               if (ack != SWD_ACK_OK && check_ack) {
+                       queue_retval = swd_ack_to_error_code(ack);
+                       goto skip;
+
+               } else if (read) {
+                       int parity = buf_get_u32(rx_ptr, CMD_BITS + TURN_BITS + 
ACK_BITS + DATA_BITS, PARITY_BITS);
+
+                       if (parity != parity_u32(data)) {
+                               LOG_ERROR("SWD Read data parity mismatch");
+                               queue_retval = ERROR_FAIL;
+                               goto skip;
+                       }
+
+                       if (queue_infos[queue_idx].rx_ptr)
+                               *queue_infos[queue_idx].rx_ptr = data;
+               }
+       }
+
+skip:
+       usb3813spi_clear_queue();
+
+       int retval = queue_retval;
+       queue_retval = ERROR_OK;
+
+       return retval;
+}
+
+static int usb3813spi_swd_run_queue(void)
+{
+       /* Since we are unsure if another SWD transaction will follow the
+        * transactions we are just about to execute, we need to add on 8 idle
+        * cycles.
+        */
+       return usb3813spi_swd_execute_queue(END_IDLE_BYTES);
+}
+
+static void usb3813spi_swd_queue_cmd(uint8_t cmd, uint32_t *dst, uint32_t 
data, uint32_t ap_delay_clk)
+{
+       unsigned int swd_op_bytes = DIV_ROUND_UP(SWD_OP_BITS + ap_delay_clk, 
SPI_BITS);
+
+       if (queue_fill >= max_queue_entries ||
+           queue_buf_fill + swd_op_bytes + END_IDLE_BYTES > queue_buf_size) {
+               /* Not enough room in the queue. Run the queue. No idle bytes 
are
+                * needed because we are going to execute transactions right 
after
+                * the queue is emptied.
+                */
+               queue_retval = usb3813spi_swd_execute_queue(0);
+       }
+
+       if (queue_retval != ERROR_OK)
+               return;
+
+       uint8_t *tx_ptr = &queue_tx_buf[queue_buf_fill];
+
+       cmd |= SWD_CMD_START | SWD_CMD_PARK;
+
+       buf_set_u32(tx_ptr, 0, CMD_BITS, cmd);
+
+       if (cmd & SWD_CMD_RNW) {
+               // Queue a read transaction
+               queue_infos[queue_fill].rx_ptr = dst;
+       } else {
+               // Queue a write transaction
+               buf_set_u32(tx_ptr,
+                       CMD_BITS + TURN_BITS + ACK_BITS + TURN_BITS, DATA_BITS, 
data);
+               buf_set_u32(tx_ptr,
+                       CMD_BITS + TURN_BITS + ACK_BITS + TURN_BITS + 
DATA_BITS, PARITY_BITS, parity_u32(data));
+       }
+
+       queue_infos[queue_fill].buf_idx = queue_buf_fill;
+
+       /* Add idle cycles after AP accesses to avoid WAIT. Buffer is already
+        * zeroed so we just need to advance the pointer to add idle cycles.
+        */
+       queue_buf_fill += swd_op_bytes;
+
+       queue_fill++;
+}
+
+static void usb3813spi_swd_read_reg(uint8_t cmd, uint32_t *value, uint32_t 
ap_delay_clk)
+{
+       assert(cmd & SWD_CMD_RNW);
+       usb3813spi_swd_queue_cmd(cmd, value, 0, ap_delay_clk);
+}
+
+static void usb3813spi_swd_write_reg(uint8_t cmd, uint32_t value, uint32_t 
ap_delay_clk)
+{
+       assert(!(cmd & SWD_CMD_RNW));
+       usb3813spi_swd_queue_cmd(cmd, NULL, value, ap_delay_clk);
+}
+
+static int usb3813spi_swd_switch_seq(enum swd_special_seq seq)
+{
+       switch (seq) {
+       case LINE_RESET:
+               LOG_DEBUG_IO("SWD line reset");
+               spi_exchange(swd_seq_line_reset, NULL, swd_seq_line_reset_len / 
SPI_BITS);
+               break;
+       case JTAG_TO_SWD:
+               LOG_DEBUG("JTAG-to-SWD");
+               spi_exchange(swd_seq_jtag_to_swd, NULL, swd_seq_jtag_to_swd_len 
/ SPI_BITS);
+               break;
+       case JTAG_TO_DORMANT:
+               LOG_DEBUG("JTAG-to-DORMANT");
+               spi_exchange(swd_seq_jtag_to_dormant, NULL, 
swd_seq_jtag_to_dormant_len / SPI_BITS);
+               break;
+       case SWD_TO_JTAG:
+               LOG_DEBUG("SWD-to-JTAG");
+               spi_exchange(swd_seq_swd_to_jtag, NULL, swd_seq_swd_to_jtag_len 
/ SPI_BITS);
+               break;
+       case SWD_TO_DORMANT:
+               LOG_DEBUG("SWD-to-DORMANT");
+               spi_exchange(swd_seq_swd_to_dormant, NULL, 
swd_seq_swd_to_dormant_len / SPI_BITS);
+               break;
+       case DORMANT_TO_SWD:
+               LOG_DEBUG("DORMANT-to-SWD");
+               spi_exchange(swd_seq_dormant_to_swd, NULL, 
swd_seq_dormant_to_swd_len / SPI_BITS);
+               break;
+       case DORMANT_TO_JTAG:
+               LOG_DEBUG("DORMANT-to-JTAG");
+               spi_exchange(swd_seq_dormant_to_jtag, NULL, 
swd_seq_dormant_to_jtag_len / SPI_BITS);
+               break;
+       default:
+               LOG_ERROR("Sequence %d not supported", seq);
+               return ERROR_FAIL;
+       }
+
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(usb3813spi_handle_vid_pid_command)
+{
+       if (CMD_ARGC != 2)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       COMMAND_PARSE_NUMBER(u16, CMD_ARGV[0], spi_vid);
+       COMMAND_PARSE_NUMBER(u16, CMD_ARGV[1], spi_pid);
+
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(usb3813spi_handle_hub_vid_pid_command)
+{
+       if (CMD_ARGC != 2)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       COMMAND_PARSE_NUMBER(u16, CMD_ARGV[0], hub_vid);
+       COMMAND_PARSE_NUMBER(u16, CMD_ARGV[1], hub_pid);
+
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(usb3813spi_handle_mode_command)
+{
+       if (CMD_ARGC != 1)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       COMMAND_PARSE_NUMBER(u32, CMD_ARGV[0], spi_mode);
+
+       if (spi_mode != 1 && spi_mode != 3) {
+               LOG_DEBUG("spi_mode %u not supported. Setting spi_mode to 3.", 
spi_mode);
+               spi_mode = 3;
+       }
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(usb3813spi_handle_queue_entries_command)
+{
+       uint32_t new_queue_entries;
+
+       if (CMD_ARGC != 1)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       COMMAND_PARSE_NUMBER(u32, CMD_ARGV[0], new_queue_entries);
+
+       if (new_queue_entries > MAX_QUEUE_ENTRIES) {
+               new_queue_entries = MAX_QUEUE_ENTRIES;
+               LOG_DEBUG("Max queue_entries exceeded. Setting queue_entries to 
32.");
+       }
+       return usb3813spi_alloc_queue(new_queue_entries);
+}
+
+const struct swd_driver usb3813spi_swd = {
+       .init = usb3813spi_swd_init,
+       .switch_seq = usb3813spi_swd_switch_seq,
+       .read_reg = usb3813spi_swd_read_reg,
+       .write_reg = usb3813spi_swd_write_reg,
+       .run = usb3813spi_swd_run_queue,
+};
+
+static const struct command_registration usb3813spi_subcommand_handlers[] = {
+       {
+               .name = "vid_pid",
+               .handler = &usb3813spi_handle_vid_pid_command,
+               .mode = COMMAND_CONFIG,
+               .help = "set the VID and PID of the USB device",
+               .usage = "vid pid",
+       },
+       {
+               .name = "hub_vid_pid",
+               .handler = &usb3813spi_handle_hub_vid_pid_command,
+               .mode = COMMAND_CONFIG,
+               .help = "set the VID and PID of the USB hub",
+               .usage = "vid pid",
+       },
+       {
+               .name = "mode",
+               .handler = &usb3813spi_handle_mode_command,
+               .mode = COMMAND_CONFIG,
+               .help = "set the mode of the spi port (0 or 3)",
+               .usage = "mode",
+       },
+       {
+               .name = "queue_entries",
+               .handler = &usb3813spi_handle_queue_entries_command,
+               .mode = COMMAND_CONFIG,
+               .help = "set the queue entry size (default=32, max=32)",
+               .usage = "queue_entries",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+static const struct command_registration usb3813spi_command_handlers[] = {
+       {
+               .name = "usb3813spi",
+               .mode = COMMAND_ANY,
+               .help = "perform usb3813spi management",
+               .chain = usb3813spi_subcommand_handlers,
+               .usage = "",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+struct adapter_driver usb3813spi_adapter_driver = {
+       .name = "usb3813spi",
+       .transport_ids = TRANSPORT_SWD,
+       .transport_preferred_id = TRANSPORT_SWD,
+       .commands = usb3813spi_command_handlers,
+
+       .init = usb3813spi_init,
+       .quit = usb3813spi_quit,
+       // speed can only be changed with strap resistors
+       .swd_ops = &usb3813spi_swd,
+};
diff --git a/src/jtag/interface.h b/src/jtag/interface.h
index 51cbf9ac0d..c722f2a4a4 100644
--- a/src/jtag/interface.h
+++ b/src/jtag/interface.h
@@ -396,6 +396,7 @@ extern struct adapter_driver jtag_vpi_adapter_driver;
 extern struct adapter_driver kitprog_adapter_driver;
 extern struct adapter_driver linuxgpiod_adapter_driver;
 extern struct adapter_driver linuxspidev_adapter_driver;
+extern struct adapter_driver usb3813spi_adapter_driver;
 extern struct adapter_driver opendous_adapter_driver;
 extern struct adapter_driver openjtag_adapter_driver;
 extern struct adapter_driver osbdm_adapter_driver;
diff --git a/src/jtag/interfaces.c b/src/jtag/interfaces.c
index caf5ace999..a034a06f4c 100644
--- a/src/jtag/interfaces.c
+++ b/src/jtag/interfaces.c
@@ -110,6 +110,9 @@ struct adapter_driver *adapter_drivers[] = {
 #if BUILD_LINUXSPIDEV == 1
                &linuxspidev_adapter_driver,
 #endif
+#if BUILD_USB3813SPI == 1
+               &usb3813spi_adapter_driver,
+#endif
 #if BUILD_OPENDOUS == 1
                &opendous_adapter_driver,
 #endif

-- 

Reply via email to