This is an automated email from Gerrit.

"Name of user not set <[email protected]>" just uploaded a new patch 
set to Gerrit, which you can find at https://review.openocd.org/c/openocd/+/9586

-- gerrit

commit e37057465e6fe31036447986ec47cbf28db35684
Author: William Markezana <[email protected]>
Date:   Fri Apr 10 12:45:43 2026 -0400

    jtag/drivers: add T-Head CK-Link JTAG adapter driver
    
    Add support for the T-Head CK-Link Lite V2 probe family, including the
    Bouffalo Lab BL616/BL702 CK-Link clones used as built-in debuggers on
    BL618/BL702 evaluation boards. The wire protocol is a framed command
    format over USB bulk endpoints, reverse-engineered from T-Head's
    DebugServer binary.
    
    The driver implements:
    
      - Full DebugServer init sequence: selfreg setup, operational clock,
        5-wire -> 2-wire -> 5-wire mode toggle (effective TAP reset).
      - JTAG_SCAN via the probe's single-entry batch opcode with IR caching
        across execute_queue boundaries; end-of-queue flush populates the
        IR scan's in_value with hardware-captured Capture-IR data.
      - Opcode-aware response-frame checksum validation. Selfreg reads use
        sum-mod-256; JTAG batch responses exclude the last dr_tdo byte.
      - Clock-divider control (freq_kHz = 48000 / (N+1)) with once-warning
        clamp when the requested speed exceeds the DebugServer-validated
        ceiling (2526 kHz).
      - libusb serial-number filter via adapter_get_required_serial()
        and kernel-driver auto-detach on Linux.
      - TCL command 'cklink vid_pid (vid pid)+' to override defaults.
    
    Known protocol limitations, surfaced as one-time warnings rather than
    silent data loss:
    
      - Scans exceed 255 bits: the batch opcode encodes bit-counts in single
        bytes. Clamp + TDI pass-through for the un-shifted portion, which
        gives OpenOCD's bypass chain-length probe the expected end-of-chain
        marker.
      - Back-to-back IR scans: the batch opcode requires both IR and DR
        phases; mid-queue flushing a deferred IR with a dummy DR would
        trigger spurious DMI transactions. Single-TAP chains only.
      - JTAG_TMS, JTAG_PATHMOVE: no TMS-sequence opcode exposed. Return
        ERROR_JTAG_NOT_IMPLEMENTED.
      - RTCK: not supported, return ERROR_JTAG_NOT_IMPLEMENTED.
    
    Tested against BL618 (VID 0x42bf, PID 0xb210) and BL702 eval boards:
    IDCODE read, RISC-V DM examine, halt, register read, memory read,
    single-step, hardware breakpoints, GDB RSP server all functional.
    
    Change-Id: If6adaec0445748f3e97078bf980c502eff4d5001
    Signed-off-by: William Markezana <[email protected]>

diff --git a/NEWS b/NEWS
index 9db6c5feee..4b77a446d1 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,8 @@ This file includes highlights of the changes made in the OpenOCD
 source archive release.
 
 JTAG Layer:
+       * Added support for T-Head CK-Link Lite JTAG adapters, including the
+         Bouffalo Lab BL616/BL702 clone probes (BL618 eval board debugger).
 
 Boundary Scan:
 
diff --git a/configure.ac b/configure.ac
index 0d5f0eb354..515f4af119 100644
--- a/configure.ac
+++ b/configure.ac
@@ -128,6 +128,7 @@ m4_define([USB1_ADAPTERS],
        [[[ftdi], [MPSSE mode of FTDI based devices], [FTDI]],
        [[ftdi_cjtag], [cJTAG (OScan1, JScan3) tunneled thru MPSSE], 
[FTDI_CJTAG]],
        [[ch347], [CH347 based devices], [CH347]],
+       [[cklink], [T-Head CK-Link JTAG Probe], [CKLINK]],
        [[stlink], [ST-Link Programmer], [HLADAPTER_STLINK]],
        [[ti_icdi], [TI ICDI JTAG Programmer], [HLADAPTER_ICDI]],
        [[ulink], [Keil ULINK JTAG Programmer], [ULINK]],
diff --git a/contrib/60-openocd.rules b/contrib/60-openocd.rules
index 151b4e2c06..0f49e1a2c0 100644
--- a/contrib/60-openocd.rules
+++ b/contrib/60-openocd.rules
@@ -269,4 +269,8 @@ ATTRS{idVendor}=="c251", ATTRS{idProduct}=="2750", 
MODE="660", GROUP="plugdev",
 # CMSIS-DAP compatible adapters
 ATTRS{product}=="*CMSIS-DAP*", MODE="660", GROUP="plugdev", TAG+="uaccess"
 
+# T-Head CK-Link Lite and Bouffalo Lab BL616/BL702 clones
+ATTRS{idVendor}=="32bf", ATTRS{idProduct}=="b210", MODE="660", 
GROUP="plugdev", TAG+="uaccess"
+ATTRS{idVendor}=="42bf", ATTRS{idProduct}=="b210", MODE="660", 
GROUP="plugdev", TAG+="uaccess"
+
 LABEL="openocd_rules_end"
diff --git a/doc/openocd.texi b/doc/openocd.texi
index a21a3e4bda..d016cc5d3c 100644
--- a/doc/openocd.texi
+++ b/doc/openocd.texi
@@ -2615,6 +2615,24 @@ ch347 device_desc "EasyDevKit"
 @end deffn
 @end deffn
 
+@deffn {Interface Driver} {cklink}
+Driver for the T-Head CK-Link Lite V2 JTAG probe and Bouffalo Lab
+BL616/BL702 clones, including the built-in debugger on Bouffalo BL618
+evaluation boards. Communication is over USB bulk endpoints using a
+T-Head DebugServer-compatible framed command protocol.
+
+The driver only supports JTAG transport and a single-TAP chain. The
+probe runs at a fixed clock; @command{adapter speed} requests are
+accepted but ignored (with a one-time warning). @command{JTAG_TMS} and
+@command{JTAG_PATHMOVE} are not supported by this probe's protocol.
+
+@deffn {Config Command} {cklink vid_pid} [vid pid]+
+Override the built-in VID/PID table. By default the driver matches
+0x42bf/0xb210 (Bouffalo) and 0x32bf/0xb210 (T-Head). Up to eight
+@var{vid}/@var{pid} pairs may be specified.
+@end deffn
+@end deffn
+
 @deffn {Interface Driver} {cmsis-dap}
 ARM CMSIS-DAP compliant based adapter v1 (USB HID based)
 or v2 (USB bulk or TCP/IP).
diff --git a/src/jtag/drivers/Makefile.am b/src/jtag/drivers/Makefile.am
index 5216e5d774..ad08b0e0c8 100644
--- a/src/jtag/drivers/Makefile.am
+++ b/src/jtag/drivers/Makefile.am
@@ -213,6 +213,9 @@ endif
 if CH347
 DRIVERFILES += %D%/ch347.c
 endif
+if CKLINK
+DRIVERFILES += %D%/cklink.c
+endif
 
 DRIVERHEADERS = \
        %D%/bitbang.h \
diff --git a/src/jtag/drivers/cklink.c b/src/jtag/drivers/cklink.c
new file mode 100644
index 0000000000..efbbd85a44
--- /dev/null
+++ b/src/jtag/drivers/cklink.c
@@ -0,0 +1,802 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/***************************************************************************
+ *   T-Head CK-Link JTAG adapter driver.                                   *
+ *                                                                         *
+ *   Supports the CK-Link Lite V2 wire protocol used by T-Head probes and  *
+ *   Bouffalo Lab BL616/BL702 CK-Link clones. USB bulk with a framed       *
+ *   command protocol reverse-engineered from T-Head's DebugServer.        *
+ *                                                                         *
+ *   Copyright (C) 2026 by William Markezana                               *
+ *       <[email protected]>                                     *
+ *                                                                         *
+ ***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+
+#include <jtag/adapter.h>
+#include <jtag/interface.h>
+#include <jtag/commands.h>
+#include <helper/binarybuffer.h>
+#include <helper/bits.h>
+#include <helper/time_support.h>
+#include "libusb_helper.h"
+
+/* USB identification. VID/PID table is extendable via `cklink vid_pid`. */
+#define CKLINK_VID_BOUFFALO            0x42bf
+#define CKLINK_VID_THEAD               0x32bf
+#define CKLINK_PID_LITE_V2             0xb210
+#define CKLINK_MAX_VID_PIDS            8
+
+#define CKLINK_USB_INTERFACE           0
+#define CKLINK_EP_OUT                  0x02
+#define CKLINK_EP_IN                   0x81
+#define CKLINK_USB_TIMEOUT_MS          1000
+#define CKLINK_USB_BUF_SIZE            2048
+
+/* Wire-protocol framing bytes */
+#define CKLINK_FRAME_START             0x68
+#define CKLINK_FRAME_END               0x16
+
+/* Response frame layout: start + status + payload + checksum + end */
+#define CKLINK_FRAME_OVERHEAD          4
+#define CKLINK_RESP_PAYLOAD_OFFSET     2
+
+/* Opcodes */
+#define CKLINK_OP_SELFREG_WRITE                0x06
+#define CKLINK_OP_SELFREG_READ         0x87
+#define CKLINK_OP_JTAG_BATCH           0x88
+
+/* Self-register indices */
+#define CKLINK_SR_CSR                  0       /* clock divider + CDI mode */
+#define CKLINK_SR_MTCR_WAIT            1       /* target-comm wait cycles */
+#define CKLINK_SR_JTAG_CONFIG          8       /* JTAG config word */
+#define CKLINK_SELFREG_BYTES           4
+
+/*
+ * Init values replayed from a captured DebugServer session.
+ * sr0 (CSR): byte 0 = clock divider, byte 3 = CDI mode (0x60=5-wire JTAG,
+ * 0x61=2-wire cJTAG). The 5->2->5 mode toggle at end of init acts as a
+ * TAP reset: the wire driver re-initializes on each switch.
+ * sr1 MTCR_WAIT = 1000 cycles. sr8 JTAG_CONFIG: opaque, known-good.
+ */
+#define CKLINK_SR0_SETUP_CLK_BYTE      0x17    /* ~2000 kHz */
+#define CKLINK_SR0_RUN_CLK_BYTE                0x12    /* ~2526 kHz, 
DebugServer default */
+#define CKLINK_SR0_MODE_JTAG           0x60
+#define CKLINK_SR0_MODE_CJTAG          0x61
+#define CKLINK_SR1_INIT_VALUE          0x000003e8
+#define CKLINK_SR8_INIT_VALUE          0x00622250
+
+/* freq_kHz = 48000 / (N + 1); cap at DebugServer-validated 0x12. */
+#define CKLINK_CLK_SOURCE_KHZ          48000
+#define CKLINK_CLK_DIV_MIN             CKLINK_SR0_RUN_CLK_BYTE
+#define CKLINK_CLK_DIV_MAX             0xff
+
+#define CKLINK_CSR(clk, mode)          (((uint32_t)(mode) << 24) | (clk))
+
+/* Batch opcode encodes IR/DR bit-counts in single bytes. */
+#define CKLINK_MAX_SCAN_BITS           255
+#define CKLINK_MAX_SCAN_BYTES          32      /* DIV_ROUND_UP(255, 8) */
+
+/* Synthesized IR when a DR scan arrives without a cached IR. */
+#define CKLINK_PLACEHOLDER_IR_BITS     5
+#define CKLINK_PLACEHOLDER_IR_VALUE    0x01
+
+struct cklink_ctx {
+       struct libusb_device_handle *usb_dev;
+       uint8_t tx_buf[CKLINK_USB_BUF_SIZE];
+       uint8_t rx_buf[CKLINK_USB_BUF_SIZE];
+       uint8_t csr_clk;
+       uint8_t ir_value[CKLINK_MAX_SCAN_BYTES];
+       unsigned int ir_bits;
+       struct jtag_command *pending_ir_cmd;
+       uint8_t *scan_tdi;
+       uint8_t *scan_tdo;
+       size_t scan_buf_size;
+       bool long_scan_warned;
+       bool dropped_ir_warned;
+};
+
+static struct cklink_ctx *cklink_handle;
+
+/* VID/PID table, NULL-terminated pairs; extendable via `cklink vid_pid`. */
+static uint16_t cklink_vids[CKLINK_MAX_VID_PIDS + 1] = {
+       CKLINK_VID_BOUFFALO,
+       CKLINK_VID_THEAD,
+};
+static uint16_t cklink_pids[CKLINK_MAX_VID_PIDS + 1] = {
+       CKLINK_PID_LITE_V2,
+       CKLINK_PID_LITE_V2,
+};
+
+/* Sum-mod-256. */
+static uint8_t cklink_checksum(const uint8_t *buf, int len)
+{
+       unsigned int sum = 0;
+       for (int i = 0; i < len; i++)
+               sum += buf[i];
+       return (uint8_t)(sum & 0xff);
+}
+
+/* Send a framed command; NULL rxlen means fire-and-forget (selfreg writes
+ * are not acknowledged). @p opcode selects the response-checksum rule. */
+static int cklink_usb_xfer(struct cklink_ctx *ck, uint8_t opcode,
+                          int txlen, int *rxlen)
+{
+       int actual = 0;
+       int ret = jtag_libusb_bulk_write(ck->usb_dev, CKLINK_EP_OUT,
+                                        (char *)ck->tx_buf, txlen,
+                                        CKLINK_USB_TIMEOUT_MS, &actual);
+       if (ret != ERROR_OK || actual != txlen) {
+               LOG_ERROR("CK-Link bulk write failed (ret=%d, actual=%d/%d)",
+                         ret, actual, txlen);
+               return ERROR_FAIL;
+       }
+
+       if (!rxlen)
+               return ERROR_OK;
+
+       ret = jtag_libusb_bulk_read(ck->usb_dev, CKLINK_EP_IN,
+                                   (char *)ck->rx_buf, CKLINK_USB_BUF_SIZE,
+                                   CKLINK_USB_TIMEOUT_MS, &actual);
+       if (ret != ERROR_OK || actual < CKLINK_FRAME_OVERHEAD) {
+               LOG_ERROR("CK-Link bulk read failed (ret=%d, actual=%d)",
+                         ret, actual);
+               return ERROR_FAIL;
+       }
+       if (ck->rx_buf[0] != CKLINK_FRAME_START) {
+               LOG_ERROR("CK-Link bad response start byte 0x%02x",
+                         ck->rx_buf[0]);
+               return ERROR_FAIL;
+       }
+       if (ck->rx_buf[actual - 1] != CKLINK_FRAME_END) {
+               LOG_ERROR("CK-Link bad response end byte 0x%02x",
+                         ck->rx_buf[actual - 1]);
+               return ERROR_FAIL;
+       }
+       /*
+        * RX checksum is opcode-specific: selfreg reads sum all payload;
+        * single-entry JTAG batch sums all except the last byte of dr_tdo.
+        */
+       int csum_end = (opcode == CKLINK_OP_JTAG_BATCH) ? actual - 3
+                                                      : actual - 2;
+       uint8_t computed = cklink_checksum(ck->rx_buf, csum_end);
+       if (computed != ck->rx_buf[actual - 2]) {
+               LOG_ERROR("CK-Link response checksum mismatch: op=0x%02x 
stated=0x%02x computed=0x%02x",
+                         opcode, ck->rx_buf[actual - 2], computed);
+               return ERROR_FAIL;
+       }
+       /* Status byte semantics undocumented; log non-zero for traces. */
+       if (ck->rx_buf[1] != 0) {
+               LOG_DEBUG("CK-Link op=0x%02x returned status=0x%02x",
+                         opcode, ck->rx_buf[1]);
+       }
+       *rxlen = actual;
+       return ERROR_OK;
+}
+
+static int cklink_frame_start(struct cklink_ctx *ck, uint8_t opcode)
+{
+       ck->tx_buf[0] = CKLINK_FRAME_START;
+       ck->tx_buf[1] = opcode;
+       return 2;
+}
+
+static int cklink_frame_end(struct cklink_ctx *ck, int pos)
+{
+       assert(pos + 2 <= CKLINK_USB_BUF_SIZE);
+       ck->tx_buf[pos] = cklink_checksum(ck->tx_buf, pos);
+       ck->tx_buf[pos + 1] = CKLINK_FRAME_END;
+       return pos + 2;
+}
+
+static int cklink_selfreg_write(struct cklink_ctx *ck, uint8_t reg,
+                               const uint8_t *value)
+{
+       int pos = cklink_frame_start(ck, CKLINK_OP_SELFREG_WRITE);
+       ck->tx_buf[pos++] = reg;
+       memcpy(&ck->tx_buf[pos], value, CKLINK_SELFREG_BYTES);
+       pos += CKLINK_SELFREG_BYTES;
+       pos = cklink_frame_end(ck, pos);
+       return cklink_usb_xfer(ck, CKLINK_OP_SELFREG_WRITE, pos, NULL);
+}
+
+static int cklink_selfreg_read(struct cklink_ctx *ck, uint8_t reg, uint8_t 
*value)
+{
+       int pos = cklink_frame_start(ck, CKLINK_OP_SELFREG_READ);
+       ck->tx_buf[pos++] = reg;
+       pos = cklink_frame_end(ck, pos);
+
+       int rxlen = 0;
+       int ret = cklink_usb_xfer(ck, CKLINK_OP_SELFREG_READ, pos, &rxlen);
+       if (ret != ERROR_OK)
+               return ret;
+
+       const int expected = CKLINK_FRAME_OVERHEAD + CKLINK_SELFREG_BYTES;
+       if (rxlen < expected) {
+               LOG_ERROR("CK-Link selfreg read response too short (%d < %d)",
+                         rxlen, expected);
+               return ERROR_FAIL;
+       }
+       memcpy(value, &ck->rx_buf[CKLINK_RESP_PAYLOAD_OFFSET],
+              CKLINK_SELFREG_BYTES);
+       return ERROR_OK;
+}
+
+/* Single-entry batch scan. Scans > 255 bits are clamped with a one-time
+ * warning: chunking across entries breaks Shift-DR continuity, and
+ * erroring out here breaks OpenOCD's chain probe's -expected-id recovery. */
+static int cklink_jtag_scan(struct cklink_ctx *ck,
+                           unsigned int ir_bits, const uint8_t *ir_tdi,
+                           uint8_t *ir_tdo,
+                           unsigned int dr_bits, const uint8_t *dr_tdi,
+                           uint8_t *dr_tdo)
+{
+       if ((ir_bits > CKLINK_MAX_SCAN_BITS || dr_bits > CKLINK_MAX_SCAN_BITS)
+           && !ck->long_scan_warned) {
+               LOG_WARNING("CK-Link: scan exceeds %u-bit per-entry limit 
(ir=%u dr=%u); clamping",
+                           CKLINK_MAX_SCAN_BITS, ir_bits, dr_bits);
+               ck->long_scan_warned = true;
+       }
+       if (ir_bits > CKLINK_MAX_SCAN_BITS)
+               ir_bits = CKLINK_MAX_SCAN_BITS;
+       if (dr_bits > CKLINK_MAX_SCAN_BITS)
+               dr_bits = CKLINK_MAX_SCAN_BITS;
+
+       const unsigned int ir_bytes = DIV_ROUND_UP(ir_bits, 8);
+       const unsigned int dr_bytes = DIV_ROUND_UP(dr_bits, 8);
+
+       int pos = cklink_frame_start(ck, CKLINK_OP_JTAG_BATCH);
+       ck->tx_buf[pos++] = 0;          /* entry_count - 1 */
+       ck->tx_buf[pos++] = (uint8_t)ir_bits;
+       if (ir_tdi)
+               memcpy(&ck->tx_buf[pos], ir_tdi, ir_bytes);
+       else
+               memset(&ck->tx_buf[pos], 0, ir_bytes);
+       pos += ir_bytes;
+       ck->tx_buf[pos++] = (uint8_t)dr_bits;
+       if (dr_tdi)
+               memcpy(&ck->tx_buf[pos], dr_tdi, dr_bytes);
+       else
+               memset(&ck->tx_buf[pos], 0, dr_bytes);
+       pos += dr_bytes;
+       pos = cklink_frame_end(ck, pos);
+
+       int rxlen = 0;
+       int ret = cklink_usb_xfer(ck, CKLINK_OP_JTAG_BATCH, pos, &rxlen);
+       if (ret != ERROR_OK)
+               return ret;
+
+       /* Response: start + status + ir_tdo + dr_echo + dr_tdo + csum + end. */
+       const unsigned int expected =
+               CKLINK_FRAME_OVERHEAD + ir_bytes + 1 + dr_bytes;
+       if ((unsigned int)rxlen < expected) {
+               LOG_ERROR("CK-Link scan response truncated: got %d, need %u 
(ir=%u dr=%u)",
+                         rxlen, expected, ir_bits, dr_bits);
+               return ERROR_FAIL;
+       }
+
+       if (ir_tdo && ir_bytes)
+               memcpy(ir_tdo, &ck->rx_buf[CKLINK_RESP_PAYLOAD_OFFSET], 
ir_bytes);
+
+       const unsigned int dr_tdo_offset =
+               CKLINK_RESP_PAYLOAD_OFFSET + ir_bytes + 1;
+       if (dr_tdo)
+               memcpy(dr_tdo, &ck->rx_buf[dr_tdo_offset], dr_bytes);
+
+       return ERROR_OK;
+}
+
+/* Shift num_bits of idle cycles, chunked to the 255-bit protocol limit. */
+static int cklink_idle_cycles(struct cklink_ctx *ck, unsigned int num_bits)
+{
+       static const uint8_t zeros[CKLINK_MAX_SCAN_BYTES] = { 0 };
+
+       while (num_bits > 0) {
+               unsigned int chunk = num_bits > CKLINK_MAX_SCAN_BITS
+                       ? CKLINK_MAX_SCAN_BITS : num_bits;
+               int ret = cklink_jtag_scan(ck, 0, NULL, NULL,
+                                          chunk, zeros, NULL);
+               if (ret != ERROR_OK)
+                       return ret;
+               num_bits -= chunk;
+       }
+       return ERROR_OK;
+}
+
+/* Flush a pending IR with a 1-bit dummy DR so its in_value gets populated
+ * with real captured IR TDO. Called at end of execute_queue so Capture-IR
+ * validation sees real hardware data. */
+static int cklink_flush_pending_ir(struct cklink_ctx *ck)
+{
+       if (!ck || !ck->pending_ir_cmd)
+               return ERROR_OK;
+
+       uint8_t ir_tdo[CKLINK_MAX_SCAN_BYTES] = { 0 };
+       uint8_t dummy_dr = 0;
+       int ret = cklink_jtag_scan(ck, ck->ir_bits, ck->ir_value, ir_tdo,
+                                  1, &dummy_dr, NULL);
+       if (ret != ERROR_OK)
+               return ret;
+
+       struct scan_command *scan = ck->pending_ir_cmd->cmd.scan;
+       unsigned int off = 0;
+       for (unsigned int i = 0; i < scan->num_fields; i++) {
+               struct scan_field *f = &scan->fields[i];
+               if (f->in_value) {
+                       buf_set_buf(ir_tdo, off, f->in_value, 0,
+                                   f->num_bits);
+               }
+               off += f->num_bits;
+       }
+       ck->pending_ir_cmd = NULL;
+       /* ir_bits/ir_value stay valid: hardware IR is still what we cached. */
+       return ERROR_OK;
+}
+
+static int cklink_execute_scan(struct cklink_ctx *ck, struct jtag_command *cmd)
+{
+       struct scan_command *scan = cmd->cmd.scan;
+
+       unsigned int total_bits = 0;
+       for (unsigned int i = 0; i < scan->num_fields; i++)
+               total_bits += scan->fields[i].num_bits;
+
+       if (total_bits == 0)
+               return ERROR_OK;
+
+       const unsigned int total_bytes = DIV_ROUND_UP(total_bits, 8);
+       if (total_bytes > ck->scan_buf_size) {
+               uint8_t *ntdi = realloc(ck->scan_tdi, total_bytes);
+               uint8_t *ntdo = realloc(ck->scan_tdo, total_bytes);
+               if (!ntdi || !ntdo) {
+                       LOG_ERROR("CK-Link: out of memory for %u-bit scan",
+                                 total_bits);
+                       /* Partial success is fine; freed in cklink_quit. */
+                       if (ntdi)
+                               ck->scan_tdi = ntdi;
+                       if (ntdo)
+                               ck->scan_tdo = ntdo;
+                       return ERROR_FAIL;
+               }
+               ck->scan_tdi = ntdi;
+               ck->scan_tdo = ntdo;
+               ck->scan_buf_size = total_bytes;
+       }
+       memset(ck->scan_tdi, 0, total_bytes);
+       memset(ck->scan_tdo, 0, total_bytes);
+
+       unsigned int bit_offset = 0;
+       for (unsigned int i = 0; i < scan->num_fields; i++) {
+               struct scan_field *field = &scan->fields[i];
+               if (field->out_value) {
+                       buf_set_buf(field->out_value, 0, ck->scan_tdi,
+                                   bit_offset, field->num_bits);
+               }
+               bit_offset += field->num_bits;
+       }
+
+       if (scan->ir_scan) {
+               /*
+                * Defer IR until paired with a DR (batch needs both).
+                * Back-to-back IR drops the first: mid-queue flush would
+                * cycle Capture-DR/Update-DR on the previous IR's register
+                * (e.g. DMI), triggering spurious transactions.
+                */
+               if (total_bytes > sizeof(ck->ir_value)) {
+                       LOG_ERROR("CK-Link: IR scan of %u bits exceeds cache 
(%zu bytes)",
+                                 total_bits, sizeof(ck->ir_value));
+                       return ERROR_JTAG_QUEUE_FAILED;
+               }
+               if (ck->pending_ir_cmd) {
+                       if (!ck->dropped_ir_warned) {
+                               LOG_WARNING("CK-Link: back-to-back IR scan 
dropped pending IR; multi-TAP chains are not supported");
+                               ck->dropped_ir_warned = true;
+                       } else {
+                               LOG_DEBUG("CK-Link: dropping pending IR (%u 
bits) before new IR scan",
+                                         ck->ir_bits);
+                       }
+               }
+               memcpy(ck->ir_value, ck->scan_tdi, total_bytes);
+               ck->ir_bits = total_bits;
+               ck->pending_ir_cmd = cmd;
+               return ERROR_OK;
+       }
+
+       /* No cached IR yet: inject IDCODE placeholder so chain detection 
works. */
+       const uint8_t placeholder_ir = CKLINK_PLACEHOLDER_IR_VALUE;
+       const unsigned int eff_ir_bits = ck->ir_bits ? ck->ir_bits
+                       : CKLINK_PLACEHOLDER_IR_BITS;
+       const uint8_t *eff_ir_value = ck->ir_bits ? ck->ir_value : 
&placeholder_ir;
+       if (!ck->ir_bits)
+               LOG_DEBUG("CK-Link: DR scan with no cached IR; injecting IDCODE 
placeholder");
+
+       uint8_t ir_tdo_buf[CKLINK_MAX_SCAN_BYTES] = { 0 };
+       int ret = cklink_jtag_scan(ck, eff_ir_bits, eff_ir_value, ir_tdo_buf,
+                                  total_bits, ck->scan_tdi, ck->scan_tdo);
+       if (ret != ERROR_OK)
+               return ret;
+
+       /* Scatter captured IR TDO back into the deferred IR command's fields. 
*/
+       if (ck->pending_ir_cmd) {
+               struct scan_command *ir_scan = ck->pending_ir_cmd->cmd.scan;
+               unsigned int ir_off = 0;
+               for (unsigned int i = 0; i < ir_scan->num_fields; i++) {
+                       struct scan_field *f = &ir_scan->fields[i];
+                       if (f->in_value) {
+                               buf_set_buf(ir_tdo_buf, ir_off,
+                                           f->in_value, 0, f->num_bits);
+                       }
+                       ir_off += f->num_bits;
+               }
+               ck->pending_ir_cmd = NULL;
+       }
+
+       /*
+        * For clamped scans, fill un-shifted TDO with caller's TDI (models
+        * "no more TAPs past the limit" - a bare wire). Gives OpenOCD's
+        * bypass chain-length probe the expected end-of-chain marker.
+        */
+       if (total_bits > CKLINK_MAX_SCAN_BITS) {
+               buf_set_buf(ck->scan_tdi, CKLINK_MAX_SCAN_BITS,
+                           ck->scan_tdo, CKLINK_MAX_SCAN_BITS,
+                           total_bits - CKLINK_MAX_SCAN_BITS);
+       }
+
+       bit_offset = 0;
+       for (unsigned int i = 0; i < scan->num_fields; i++) {
+               struct scan_field *field = &scan->fields[i];
+               if (field->in_value) {
+                       buf_set_buf(ck->scan_tdo, bit_offset,
+                                   field->in_value, 0, field->num_bits);
+               }
+               bit_offset += field->num_bits;
+       }
+
+       return ERROR_OK;
+}
+
+static int cklink_write_csr(struct cklink_ctx *ck, uint8_t clk, uint8_t mode)
+{
+       uint8_t buf[CKLINK_SELFREG_BYTES];
+       h_u32_to_le(buf, CKLINK_CSR(clk, mode));
+       return cklink_selfreg_write(ck, CKLINK_SR_CSR, buf);
+}
+
+/* khz -> sr0 byte 0 divider, rounded up so we never exceed the request. */
+static uint8_t cklink_khz_to_div(int khz)
+{
+       if (khz <= 0)
+               return CKLINK_CLK_DIV_MAX;
+       int n = ((CKLINK_CLK_SOURCE_KHZ + khz - 1) / khz) - 1;
+       if (n < CKLINK_CLK_DIV_MIN)
+               n = CKLINK_CLK_DIV_MIN;
+       if (n > CKLINK_CLK_DIV_MAX)
+               n = CKLINK_CLK_DIV_MAX;
+       return (uint8_t)n;
+}
+
+static int cklink_execute_tlr_reset(struct cklink_ctx *ck)
+{
+       /* Probe-side reset via 5->2->5 mode toggle; target TAP reset not 
guaranteed. */
+       int ret;
+
+       ret = cklink_write_csr(ck, ck->csr_clk, CKLINK_SR0_MODE_CJTAG);
+       if (ret != ERROR_OK)
+               return ret;
+       ret = cklink_write_csr(ck, ck->csr_clk, CKLINK_SR0_MODE_CJTAG);
+       if (ret != ERROR_OK)
+               return ret;
+       ret = cklink_write_csr(ck, ck->csr_clk, CKLINK_SR0_MODE_JTAG);
+       if (ret != ERROR_OK)
+               return ret;
+
+       ck->ir_bits = 0;
+       memset(ck->ir_value, 0, sizeof(ck->ir_value));
+       ck->pending_ir_cmd = NULL;
+       tap_set_state(TAP_RESET);
+       return ERROR_OK;
+}
+
+static int cklink_execute_runtest(struct cklink_ctx *ck, struct jtag_command 
*cmd)
+{
+       return cklink_idle_cycles(ck, cmd->cmd.runtest->num_cycles);
+}
+
+static int cklink_execute_stableclocks(struct cklink_ctx *ck, struct 
jtag_command *cmd)
+{
+       /* TMS=0 shifts only hold state in RTI; other stable states would 
advance. */
+       return cklink_idle_cycles(ck, cmd->cmd.stableclocks->num_cycles);
+}
+
+static int cklink_execute_tms(struct jtag_command *cmd)
+{
+       /* No TMS-sequence opcode: honoring JTAG_TMS is impossible. */
+       LOG_ERROR("CK-Link: JTAG_TMS (%u bits) is not supported by this probe",
+                 cmd->cmd.tms->num_bits);
+       return ERROR_JTAG_NOT_IMPLEMENTED;
+}
+
+static int cklink_execute_reset(struct cklink_ctx *ck, struct jtag_command 
*cmd)
+{
+       /* Only TRST is meaningful here: we have no separate SRST control. */
+       if (cmd->cmd.reset->trst)
+               return cklink_execute_tlr_reset(ck);
+       return ERROR_OK;
+}
+
+static int cklink_execute_queue(struct jtag_command *cmd_queue)
+{
+       struct cklink_ctx *ck = cklink_handle;
+       if (!ck)
+               return ERROR_JTAG_INIT_FAILED;
+
+       for (struct jtag_command *cmd = cmd_queue; cmd; cmd = cmd->next) {
+               int ret;
+
+               switch (cmd->type) {
+               case JTAG_SCAN:
+                       ret = cklink_execute_scan(ck, cmd);
+                       break;
+               case JTAG_TLR_RESET:
+                       ret = cklink_execute_tlr_reset(ck);
+                       break;
+               case JTAG_RUNTEST:
+                       ret = cklink_execute_runtest(ck, cmd);
+                       break;
+               case JTAG_RESET:
+                       ret = cklink_execute_reset(ck, cmd);
+                       break;
+               case JTAG_PATHMOVE:
+                       /* No TMS-sequence opcode: honoring PATHMOVE is 
impossible. */
+                       LOG_ERROR("CK-Link: JTAG_PATHMOVE is not supported by 
this probe");
+                       ret = ERROR_JTAG_NOT_IMPLEMENTED;
+                       break;
+               case JTAG_STABLECLOCKS:
+                       ret = cklink_execute_stableclocks(ck, cmd);
+                       break;
+               case JTAG_TMS:
+                       ret = cklink_execute_tms(cmd);
+                       break;
+               case JTAG_SLEEP:
+                       jtag_sleep(cmd->cmd.sleep->us);
+                       ret = ERROR_OK;
+                       break;
+               default:
+                       LOG_ERROR("CK-Link: unsupported JTAG command %d",
+                                 cmd->type);
+                       return ERROR_JTAG_QUEUE_FAILED;
+               }
+               if (ret != ERROR_OK)
+                       return ret;
+       }
+       /* Flush so any un-paired IR's in_value is populated before OpenOCD 
reads. */
+       return cklink_flush_pending_ir(ck);
+}
+
+static int cklink_probe_init(struct cklink_ctx *ck)
+{
+       uint8_t sr1[CKLINK_SELFREG_BYTES];
+       uint8_t sr8[CKLINK_SELFREG_BYTES];
+       uint8_t readback[CKLINK_SELFREG_BYTES];
+       int ret;
+
+       h_u32_to_le(sr1, CKLINK_SR1_INIT_VALUE);
+       h_u32_to_le(sr8, CKLINK_SR8_INIT_VALUE);
+
+       /* DebugServer init sequence, replayed verbatim. */
+       ret = cklink_write_csr(ck, CKLINK_SR0_SETUP_CLK_BYTE,
+                              CKLINK_SR0_MODE_JTAG);
+       if (ret != ERROR_OK)
+               return ret;
+       ret = cklink_selfreg_write(ck, CKLINK_SR_MTCR_WAIT, sr1);
+       if (ret != ERROR_OK)
+               return ret;
+       ret = cklink_selfreg_write(ck, CKLINK_SR_JTAG_CONFIG, sr8);
+       if (ret != ERROR_OK)
+               return ret;
+
+       ret = cklink_write_csr(ck, CKLINK_SR0_RUN_CLK_BYTE,
+                              CKLINK_SR0_MODE_JTAG);
+       if (ret != ERROR_OK)
+               return ret;
+
+       /* 5->2->5 mode toggle: effective TAP reset. */
+       ret = cklink_write_csr(ck, CKLINK_SR0_RUN_CLK_BYTE,
+                              CKLINK_SR0_MODE_CJTAG);
+       if (ret != ERROR_OK)
+               return ret;
+       ret = cklink_write_csr(ck, CKLINK_SR0_RUN_CLK_BYTE,
+                              CKLINK_SR0_MODE_CJTAG);
+       if (ret != ERROR_OK)
+               return ret;
+       ret = cklink_write_csr(ck, CKLINK_SR0_RUN_CLK_BYTE,
+                              CKLINK_SR0_MODE_JTAG);
+       if (ret != ERROR_OK)
+               return ret;
+
+       ret = cklink_selfreg_read(ck, CKLINK_SR_CSR, readback);
+       if (ret != ERROR_OK)
+               return ret;
+       if (readback[3] != CKLINK_SR0_MODE_JTAG) {
+               LOG_WARNING("CK-Link: sr0 mode byte is 0x%02x, expected 0x%02x 
(5-wire JTAG)",
+                           readback[3], CKLINK_SR0_MODE_JTAG);
+       }
+       ck->csr_clk = CKLINK_SR0_RUN_CLK_BYTE;
+
+       /* Pre-load IR = IDCODE; 1-bit DR is the minimum the batch opcode 
accepts. */
+       const uint8_t idcode_ir = CKLINK_PLACEHOLDER_IR_VALUE;
+       const uint8_t dummy_dr = 0;
+       return cklink_jtag_scan(ck, CKLINK_PLACEHOLDER_IR_BITS, &idcode_ir, 
NULL,
+                               1, &dummy_dr, NULL);
+}
+
+static int cklink_init(void)
+{
+       struct cklink_ctx *ck = calloc(1, sizeof(*ck));
+       if (!ck) {
+               LOG_ERROR("CK-Link: out of memory");
+               return ERROR_JTAG_INIT_FAILED;
+       }
+
+       const char *serial = adapter_get_required_serial();
+       if (jtag_libusb_open(cklink_vids, cklink_pids, serial,
+                            &ck->usb_dev, NULL) != ERROR_OK) {
+               LOG_ERROR("CK-Link probe not found");
+               free(ck);
+               return ERROR_JTAG_INIT_FAILED;
+       }
+
+       libusb_set_auto_detach_kernel_driver(ck->usb_dev, 1);
+
+       int claim = libusb_claim_interface(ck->usb_dev, CKLINK_USB_INTERFACE);
+       if (claim != 0) {
+               LOG_ERROR("CK-Link: failed to claim interface %d: %s",
+                         CKLINK_USB_INTERFACE, libusb_error_name(claim));
+               jtag_libusb_close(ck->usb_dev);
+               free(ck);
+               return ERROR_JTAG_INIT_FAILED;
+       }
+
+       if (cklink_probe_init(ck) != ERROR_OK) {
+               LOG_ERROR("CK-Link: probe initialization failed");
+               libusb_release_interface(ck->usb_dev, CKLINK_USB_INTERFACE);
+               jtag_libusb_close(ck->usb_dev);
+               free(ck);
+               return ERROR_JTAG_INIT_FAILED;
+       }
+
+       cklink_handle = ck;
+       tap_set_state(TAP_RESET);
+       LOG_INFO("CK-Link Lite V2 initialized (5-wire JTAG)");
+       return ERROR_OK;
+}
+
+static int cklink_quit(void)
+{
+       if (!cklink_handle)
+               return ERROR_OK;
+
+       libusb_release_interface(cklink_handle->usb_dev,
+                                CKLINK_USB_INTERFACE);
+       jtag_libusb_close(cklink_handle->usb_dev);
+       free(cklink_handle->scan_tdi);
+       free(cklink_handle->scan_tdo);
+       free(cklink_handle);
+       cklink_handle = NULL;
+       return ERROR_OK;
+}
+
+static int cklink_speed(int speed)
+{
+       struct cklink_ctx *ck = cklink_handle;
+       if (!ck)
+               return ERROR_OK;        /* called before init */
+
+       uint8_t div = cklink_khz_to_div(speed);
+       if (div == ck->csr_clk)
+               return ERROR_OK;
+       int ret = cklink_write_csr(ck, div, CKLINK_SR0_MODE_JTAG);
+       if (ret != ERROR_OK)
+               return ret;
+       ck->csr_clk = div;
+       int actual = CKLINK_CLK_SOURCE_KHZ / (div + 1);
+       LOG_DEBUG("CK-Link: adapter speed: requested %d kHz, programmed %d kHz 
(div=0x%02x)",
+                 speed, actual, div);
+       return ERROR_OK;
+}
+
+static int cklink_khz(int khz, int *jtag_speed)
+{
+       if (khz == 0) {
+               LOG_ERROR("CK-Link: adaptive clocking (RTCK) is not supported");
+               return ERROR_JTAG_NOT_IMPLEMENTED;
+       }
+       uint8_t div = cklink_khz_to_div(khz);
+       int achievable = CKLINK_CLK_SOURCE_KHZ / (div + 1);
+       if (achievable != khz) {
+               static int last_clamp_khz;
+               if (khz != last_clamp_khz) {
+                       LOG_WARNING("CK-Link: requested %d kHz clamped to %d 
kHz (div=0x%02x)",
+                                   khz, achievable, div);
+                       last_clamp_khz = khz;
+               }
+       }
+       *jtag_speed = achievable;
+       return ERROR_OK;
+}
+
+static int cklink_speed_div(int speed, int *khz)
+{
+       *khz = speed;
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(cklink_handle_vid_pid_command)
+{
+       if (CMD_ARGC < 2 || CMD_ARGC % 2)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       unsigned int pairs = CMD_ARGC / 2;
+       if (pairs > CKLINK_MAX_VID_PIDS) {
+               LOG_ERROR("CK-Link: too many vid/pid pairs (max %d)",
+                         CKLINK_MAX_VID_PIDS);
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+       for (unsigned int i = 0; i < pairs; i++) {
+               COMMAND_PARSE_NUMBER(u16, CMD_ARGV[2 * i], cklink_vids[i]);
+               COMMAND_PARSE_NUMBER(u16, CMD_ARGV[2 * i + 1], cklink_pids[i]);
+               if (cklink_vids[i] == 0 || cklink_pids[i] == 0) {
+                       LOG_ERROR("CK-Link: vid/pid must be non-zero (pair 
%u)", i);
+                       return ERROR_COMMAND_SYNTAX_ERROR;
+               }
+       }
+       cklink_vids[pairs] = 0;
+       cklink_pids[pairs] = 0;
+       return ERROR_OK;
+}
+
+static const struct command_registration cklink_subcommand_handlers[] = {
+       {
+               .name = "vid_pid",
+               .handler = &cklink_handle_vid_pid_command,
+               .mode = COMMAND_CONFIG,
+               .help = "VID/PID pairs of CK-Link probes to match (replaces 
defaults)",
+               .usage = "(vid pid)+",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+static const struct command_registration cklink_command_handlers[] = {
+       {
+               .name = "cklink",
+               .mode = COMMAND_ANY,
+               .help = "perform cklink management",
+               .chain = cklink_subcommand_handlers,
+               .usage = "<subcommand> [args...]",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+static struct jtag_interface cklink_jtag_interface = {
+       .execute_queue = cklink_execute_queue,
+};
+
+struct adapter_driver cklink_adapter_driver = {
+       .name                   = "cklink",
+       .transport_ids          = TRANSPORT_JTAG,
+       .transport_preferred_id = TRANSPORT_JTAG,
+       .commands               = cklink_command_handlers,
+
+       .init                   = cklink_init,
+       .quit                   = cklink_quit,
+       .speed                  = cklink_speed,
+       .khz                    = cklink_khz,
+       .speed_div              = cklink_speed_div,
+
+       .jtag_ops               = &cklink_jtag_interface,
+};
diff --git a/src/jtag/interface.h b/src/jtag/interface.h
index 51cbf9ac0d..5c6c8eea93 100644
--- a/src/jtag/interface.h
+++ b/src/jtag/interface.h
@@ -380,6 +380,7 @@ extern struct adapter_driver at91rm9200_adapter_driver;
 extern struct adapter_driver bcm2835gpio_adapter_driver;
 extern struct adapter_driver buspirate_adapter_driver;
 extern struct adapter_driver ch347_adapter_driver;
+extern struct adapter_driver cklink_adapter_driver;
 extern struct adapter_driver cmsis_dap_adapter_driver;
 extern struct adapter_driver dmem_dap_adapter_driver;
 extern struct adapter_driver dummy_adapter_driver;
diff --git a/src/jtag/interfaces.c b/src/jtag/interfaces.c
index caf5ace999..60c7364c64 100644
--- a/src/jtag/interfaces.c
+++ b/src/jtag/interfaces.c
@@ -62,6 +62,9 @@ struct adapter_driver *adapter_drivers[] = {
 #if BUILD_CH347 == 1
                &ch347_adapter_driver,
 #endif
+#if BUILD_CKLINK == 1
+               &cklink_adapter_driver,
+#endif
 #if BUILD_CMSIS_DAP_USB == 1 || BUILD_CMSIS_DAP_HID == 1 || 
BUILD_CMSIS_DAP_TCP == 1
                &cmsis_dap_adapter_driver,
 #endif
diff --git a/tcl/interface/cklink.cfg b/tcl/interface/cklink.cfg
new file mode 100644
index 0000000000..fb017aa056
--- /dev/null
+++ b/tcl/interface/cklink.cfg
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# T-Head CK-Link JTAG adapter (including Bouffalo BL616/BL702 clones)
+#
+
+adapter driver cklink

-- 


Reply via email to