jtag/drivers: add support for Nu-Link (Nuvoton ICE) over usb 35/5635/10
authorZale Yu <cyyu@nuvoton.com>
Wed, 29 Apr 2020 14:53:52 +0000 (16:53 +0200)
committerAntonio Borneo <borneo.antonio@gmail.com>
Sun, 2 Aug 2020 09:47:02 +0000 (10:47 +0100)
Add support for Nu-Link1 over usb hidapi and config file.

The original work is fetched from Nuvoton github.
Code cleanup, fix merge conflicts, compile and runtime issues.
Switch the code from libusb to hidapi, being the device HID based.
Add documentation.
Merge fixes for multi-word memory read.

Reset is not fully compatible with openocd framework; currently
the target is reset and then halt at openocd start.

Change-Id: I9738de4e26783ba462ea3e39ec32069fd5bb7d94
Signed-off-by: Zale Yu <cyyu@nuvoton.com>
Signed-off-by: Saravanan Sekar <saravanan@linumiz.com>
Signed-off-by: Antonio Borneo <borneo.antonio@gmail.com>
Reviewed-on: http://openocd.zylin.com/5635
Tested-by: jenkins
Reviewed-by: Marc Schink <dev@zapb.de>
configure.ac
contrib/60-openocd.rules
doc/openocd.texi
src/jtag/drivers/Makefile.am
src/jtag/drivers/nulink_usb.c [new file with mode: 0644]
src/jtag/hla/hla_layout.c
src/jtag/hla/hla_layout.h
tcl/interface/nulink.cfg [new file with mode: 0644]

index 4d8c916350014bb37283c07dbd94ea320e7fc73e..8bdeacdd78d57598077cac7a049ef55a8a300b13 100644 (file)
@@ -127,7 +127,8 @@ m4_define([USB0_ADAPTERS],
        [[armjtagew], [Olimex ARM-JTAG-EW Programmer], [ARMJTAGEW]]])
 
 m4_define([HIDAPI_ADAPTERS],
-       [[[cmsis_dap], [CMSIS-DAP Compliant Debugger], [CMSIS_DAP]]])
+       [[[cmsis_dap], [CMSIS-DAP Compliant Debugger], [CMSIS_DAP]],
+       [[nulink], [Nu-Link Programmer], [HLADAPTER_NULINK]]])
 
 m4_define([HIDAPI_USB1_ADAPTERS],
        [[[kitprog], [Cypress KitProg Programmer], [KITPROG]]])
@@ -696,7 +697,7 @@ AS_IF([test "x$enable_linuxgpiod" != "xno"], [
   build_bitbang=yes
 ])
 
-AS_IF([test "x$enable_stlink" != "xno" -o "x$enable_ti_icdi" != "xno"], [
+AS_IF([test "x$enable_stlink" != "xno" -o "x$enable_ti_icdi" != "xno" -o "x$enable_nulink" != "xno"], [
        AC_DEFINE([BUILD_HLADAPTER], [1], [1 if you want the High Level JTAG driver.])
        AM_CONDITIONAL([HLADAPTER], [true])
 ], [
@@ -705,6 +706,7 @@ AS_IF([test "x$enable_stlink" != "xno" -o "x$enable_ti_icdi" != "xno"], [
 ])
 AM_CONDITIONAL([HLADAPTER_STLINK], [test "x$enable_stlink" != "xno"])
 AM_CONDITIONAL([HLADAPTER_ICDI], [test "x$enable_ti_icdi" != "xno"])
+AM_CONDITIONAL([HLADAPTER_NULINK], [test "x$enable_nulink" != "xno"])
 
 AS_IF([test "x$enable_jlink" != "xno"], [
   AS_IF([test "x$use_internal_libjaylink" = "xyes"], [
index 74fc84da6c0debd1f69a6f41a6e604a18140fcc6..be27590ba9f24622937df2902a931c2388e56ebb 100644 (file)
@@ -55,6 +55,11 @@ ATTRS{idVendor}=="0403", ATTRS{idProduct}=="c141", MODE="660", GROUP="plugdev",
 # Amontec JTAGkey and JTAGkey-tiny
 ATTRS{idVendor}=="0403", ATTRS{idProduct}=="cff8", MODE="660", GROUP="plugdev", TAG+="uaccess"
 
+# Nuvoton NuLink
+ATTRS{idVendor}=="0416", ATTRS{idProduct}=="511b", MODE="660", GROUP="plugdev", TAG+="uaccess"
+ATTRS{idVendor}=="0416", ATTRS{idProduct}=="511c", MODE="660", GROUP="plugdev", TAG+="uaccess"
+ATTRS{idVendor}=="0416", ATTRS{idProduct}=="511d", MODE="660", GROUP="plugdev", TAG+="uaccess"
+
 # TI ICDI
 ATTRS{idVendor}=="0451", ATTRS{idProduct}=="c32a", MODE="660", GROUP="plugdev", TAG+="uaccess"
 
index 7a9b090d0119c5a1bd0ffae8e4a27ee2dead4e3b..958cde49e8d531a9ca98340d7af0e3aec2d541b9 100644 (file)
@@ -505,6 +505,12 @@ Texas Instruments has an adapter called @b{ICDI}.
 It is not to be confused with the FTDI based adapters that were originally fitted to their
 evaluation boards. This is the adapter fitted to the Stellaris LaunchPad.
 
+@section USB Nuvoton Nu-Link
+Nuvoton has an adapter called @b{Nu-Link}.
+It is available either as stand-alone dongle and embedded on development boards.
+It supports SWD, serial port bridge and mass storage for firmware update.
+Only Nu-Link v1 is currently supported.
+
 @section USB CMSIS-DAP based
 ARM has released a interface standard called CMSIS-DAP that simplifies connecting
 debuggers to ARM Cortex based targets @url{http://www.keil.com/support/man/docs/dapdebug/dapdebug_introduction.htm}.
@@ -3064,7 +3070,8 @@ This is a driver that supports multiple High Level Adapters.
 This type of adapter does not expose some of the lower level api's
 that OpenOCD would normally use to access the target.
 
-Currently supported adapters include the STMicroelectronics ST-LINK and TI ICDI.
+Currently supported adapters include the STMicroelectronics ST-LINK, TI ICDI
+and Nuvoton Nu-Link.
 ST-LINK firmware version >= V2.J21.S4 recommended due to issues with earlier
 versions of firmware where serial number is reset after first use.  Suggest
 using ST firmware update utility to upgrade ST-LINK firmware even if current
@@ -3078,7 +3085,7 @@ Currently Not Supported.
 Specifies the serial number of the adapter.
 @end deffn
 
-@deffn {Config Command} {hla_layout} (@option{stlink}|@option{icdi})
+@deffn {Config Command} {hla_layout} (@option{stlink}|@option{icdi}|@option{nulink})
 Specifies the adapter layout to use.
 @end deffn
 
index 77d6fb2af873acfd49d00d00304907c08f91cd71..c860833b3178894aa88d19032d99788b6b115c8b 100644 (file)
@@ -143,6 +143,9 @@ endif
 if HLADAPTER_ICDI
 DRIVERFILES += %D%/ti_icdi_usb.c
 endif
+if HLADAPTER_NULINK
+DRIVERFILES += %D%/nulink_usb.c
+endif
 if RSHIM
 DRIVERFILES += %D%/rshim.c
 endif
diff --git a/src/jtag/drivers/nulink_usb.c b/src/jtag/drivers/nulink_usb.c
new file mode 100644 (file)
index 0000000..3b0885b
--- /dev/null
@@ -0,0 +1,1104 @@
+/***************************************************************************
+ *   Copyright (C) 2016-2017 by Nuvoton                                    *
+ *   Zale Yu <cyyu@nuvoton.com>                                            *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* project specific includes */
+#include <helper/binarybuffer.h>
+#include <jtag/interface.h>
+#include <jtag/hla/hla_layout.h>
+#include <jtag/hla/hla_transport.h>
+#include <jtag/hla/hla_interface.h>
+#include <target/target.h>
+
+#include <target/cortex_m.h>
+
+#include <hidapi.h>
+
+#define NULINK_READ_TIMEOUT  1000
+
+#define NULINK_HID_MAX_SIZE   (64)
+#define V6M_MAX_COMMAND_LENGTH (NULINK_HID_MAX_SIZE - 2)
+
+struct nulink_usb_handle_s {
+       hid_device *dev_handle;
+       uint16_t max_packet_size;
+       uint8_t usbcmdidx;
+       uint8_t cmdidx;
+       uint8_t cmdbuf[NULINK_HID_MAX_SIZE + 1];
+       uint8_t tempbuf[NULINK_HID_MAX_SIZE];
+       uint8_t databuf[NULINK_HID_MAX_SIZE];
+       uint32_t max_mem_packet;
+       uint16_t hardware_config; /* bit 0: 1:Nu-Link-Pro, 0:Nu-Link */
+};
+
+/* ICE Command */
+#define CMD_READ_REG                           0xB5UL
+#define CMD_READ_RAM                           0xB1UL
+#define CMD_WRITE_REG                          0xB8UL
+#define CMD_WRITE_RAM                          0xB9UL
+#define CMD_CHECK_ID                           0xA3UL
+#define CMD_MCU_RESET                          0xE2UL
+#define CMD_CHECK_MCU_STOP                     0xD8UL
+#define CMD_MCU_STEP_RUN                       0xD1UL
+#define CMD_MCU_STOP_RUN                       0xD2UL
+#define CMD_MCU_FREE_RUN                       0xD3UL
+#define CMD_SET_CONFIG                         0xA2UL
+
+#define ARM_SRAM_BASE                          0x20000000UL
+
+#define HARDWARE_CONFIG_NULINKPRO      1
+
+enum nulink_reset {
+       RESET_AUTO = 0,
+       RESET_HW = 1,
+       RESET_SYSRESETREQ = 2,
+       RESET_VECTRESET = 3,
+       RESET_FAST_RESCUE = 4, /* Rescue and erase the chip, need very fast speed */
+};
+
+enum nulink_connect {
+       CONNECT_NORMAL = 0,      /* Support all reset method */
+       CONNECT_PRE_RESET = 1,   /* Support all reset method */
+       CONNECT_UNDER_RESET = 2, /* Support all reset method */
+       CONNECT_NONE = 3,        /* Support RESET_HW, (RESET_AUTO = RESET_HW) */
+       CONNECT_DISCONNECT = 4,  /* Support RESET_NONE, (RESET_AUTO = RESET_NONE) */
+       CONNECT_ICP_MODE = 5     /* Support NUC505 ICP mode*/
+};
+
+static int nulink_usb_xfer_rw(void *handle, uint8_t *buf)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       assert(handle);
+
+       int ret = hid_write(h->dev_handle, h->cmdbuf, h->max_packet_size + 1);
+       if (ret < 0) {
+               LOG_ERROR("hid_write");
+               return ERROR_FAIL;
+       }
+
+       ret = hid_read_timeout(h->dev_handle, buf, h->max_packet_size, NULINK_READ_TIMEOUT);
+       if (ret < 0) {
+               LOG_ERROR("hid_read_timeout");
+               return ERROR_FAIL;
+       }
+       return ERROR_OK;
+}
+
+static int nulink_usb_xfer(void *handle, uint8_t *buf, int size)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       assert(handle);
+
+       int err = nulink_usb_xfer_rw(h, h->tempbuf);
+
+       memcpy(buf, h->tempbuf + 2, V6M_MAX_COMMAND_LENGTH);
+
+       return err;
+}
+
+static void nulink_usb_init_buffer(void *handle, uint32_t size)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       h->cmdidx = 0;
+
+       memset(h->cmdbuf, 0, h->max_packet_size + 1);
+       memset(h->tempbuf, 0, h->max_packet_size);
+       memset(h->databuf, 0, h->max_packet_size);
+
+       h->cmdbuf[0] = 0; /* report number */
+       h->cmdbuf[1] = ++h->usbcmdidx & 0x7F;
+       h->cmdbuf[2] = size;
+       h->cmdidx += 3;
+}
+
+static int nulink_usb_version(void *handle)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       LOG_DEBUG("nulink_usb_version");
+
+       assert(handle);
+
+       nulink_usb_init_buffer(handle, V6M_MAX_COMMAND_LENGTH);
+
+       memset(h->cmdbuf + h->cmdidx, 0xFF, V6M_MAX_COMMAND_LENGTH);
+       h->cmdbuf[h->cmdidx + 4] = 0xA1; /* host_rev_num: 6561 */;
+       h->cmdbuf[h->cmdidx + 5] = 0x19;
+
+       int res = nulink_usb_xfer(handle, h->databuf, 4 * 5);
+       if (res != ERROR_OK)
+               return res;
+
+       LOG_INFO("Nu-Link firmware_version %" PRIu32 ", product_id (0x%08" PRIx32 ")",
+                        le_to_h_u32(h->databuf),
+                        le_to_h_u32(h->databuf + 4 * 1));
+
+       const bool is_nulinkpro = !!(le_to_h_u32(h->databuf + 4 * 2) & 1);
+       if (is_nulinkpro) {
+               LOG_INFO("Adapter is Nu-Link-Pro, target_voltage_mv(%" PRIu16 "), usb_voltage_mv(%" PRIu16 ")",
+                                le_to_h_u16(h->databuf + 4 * 3 + 0),
+                                le_to_h_u16(h->databuf + 4 * 3 + 2));
+
+               h->hardware_config |= HARDWARE_CONFIG_NULINKPRO;
+       } else {
+               LOG_INFO("Adapter is Nu-Link");
+       }
+
+       return ERROR_OK;
+}
+
+static int nulink_usb_idcode(void *handle, uint32_t *idcode)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       LOG_DEBUG("nulink_usb_idcode");
+
+       assert(handle);
+
+       nulink_usb_init_buffer(handle, 4 * 1);
+       /* set command ID */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_CHECK_ID);
+       h->cmdidx += 4;
+
+       int res = nulink_usb_xfer(handle, h->databuf, 4 * 2);
+       if (res != ERROR_OK)
+               return res;
+
+       *idcode = le_to_h_u32(h->databuf + 4 * 1);
+
+       LOG_INFO("IDCODE: 0x%08" PRIX32, *idcode);
+
+       return ERROR_OK;
+}
+
+static int nulink_usb_write_debug_reg(void *handle, uint32_t addr, uint32_t val)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       LOG_DEBUG("nulink_usb_write_debug_reg 0x%08" PRIX32 "0x%08" PRIX32, addr, val);
+
+       nulink_usb_init_buffer(handle, 8 + 12 * 1);
+       /* set command ID */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_WRITE_RAM);
+       h->cmdidx += 4;
+       /* Count of registers */
+       h->cmdbuf[h->cmdidx] = 1;
+       h->cmdidx += 1;
+       /* Array of bool value (u8ReadOld) */
+       h->cmdbuf[h->cmdidx] = 0x00;
+       h->cmdidx += 1;
+       /* Array of bool value (u8Verify) */
+       h->cmdbuf[h->cmdidx] = 0x00;
+       h->cmdidx += 1;
+       /* ignore */
+       h->cmdbuf[h->cmdidx] = 0;
+       h->cmdidx += 1;
+       /* u32Addr */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, addr);
+       h->cmdidx += 4;
+       /* u32Data */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, val);
+       h->cmdidx += 4;
+       /* u32Mask */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, 0x00000000UL);
+       h->cmdidx += 4;
+
+       return nulink_usb_xfer(handle, h->databuf, 4 * 2);
+}
+
+static enum target_state nulink_usb_state(void *handle)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       assert(handle);
+
+       nulink_usb_init_buffer(handle, 4 * 1);
+       /* set command ID */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_CHECK_MCU_STOP);
+       h->cmdidx += 4;
+
+       int res = nulink_usb_xfer(handle, h->databuf, 4 * 4);
+       if (res != ERROR_OK)
+               return TARGET_UNKNOWN;
+
+       if (!le_to_h_u32(h->databuf + 4 * 2))
+               return TARGET_HALTED;
+       else
+               return TARGET_RUNNING;
+}
+
+static int nulink_usb_assert_srst(void *handle, int srst)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       LOG_DEBUG("nulink_usb_assert_srst");
+
+       assert(handle);
+
+       nulink_usb_init_buffer(handle, 4 * 4);
+       /* set command ID */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_MCU_RESET);
+       h->cmdidx += 4;
+       /* set reset type */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, RESET_SYSRESETREQ);
+       h->cmdidx += 4;
+       /* set connect type */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, CONNECT_NORMAL);
+       h->cmdidx += 4;
+       /* set extMode */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, 0);
+       h->cmdidx += 4;
+
+       return nulink_usb_xfer(handle, h->databuf, 4 * 4);
+}
+
+static int nulink_usb_reset(void *handle)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       LOG_DEBUG("nulink_usb_reset");
+
+       assert(handle);
+
+       nulink_usb_init_buffer(handle, 4 * 4);
+       /* set command ID */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_MCU_RESET);
+       h->cmdidx += 4;
+       /* set reset type */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, RESET_HW);
+       h->cmdidx += 4;
+       /* set connect type */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, CONNECT_NORMAL);
+       h->cmdidx += 4;
+       /* set extMode */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, 0);
+       h->cmdidx += 4;
+
+       return nulink_usb_xfer(handle, h->databuf, 4 * 4);
+}
+
+static int nulink_usb_run(void *handle)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       LOG_DEBUG("nulink_usb_run");
+
+       assert(handle);
+
+       nulink_usb_init_buffer(handle, 4 * 1);
+       /* set command ID */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_MCU_FREE_RUN);
+       h->cmdidx += 4;
+
+       return nulink_usb_xfer(handle, h->databuf, 4 * 4);
+}
+
+static int nulink_usb_halt(void *handle)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       LOG_DEBUG("nulink_usb_halt");
+
+       assert(handle);
+
+       nulink_usb_init_buffer(handle, 4 * 1);
+       /* set command ID */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_MCU_STOP_RUN);
+       h->cmdidx += 4;
+
+       int res = nulink_usb_xfer(handle, h->databuf, 4 * 4);
+
+       LOG_DEBUG("Nu-Link stop_pc 0x%08" PRIx32, le_to_h_u32(h->databuf + 4));
+
+       return res;
+}
+
+static int nulink_usb_step(void *handle)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       LOG_DEBUG("nulink_usb_step");
+
+       assert(handle);
+
+       nulink_usb_init_buffer(handle, 4 * 1);
+       /* set command ID */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_MCU_STEP_RUN);
+       h->cmdidx += 4;
+
+       int res = nulink_usb_xfer(handle, h->databuf, 4 * 4);
+
+       LOG_DEBUG("Nu-Link pc 0x%08" PRIx32, le_to_h_u32(h->databuf + 4));
+
+       return res;
+}
+
+static int nulink_usb_read_reg(void *handle, int num, uint32_t *val)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       assert(handle);
+
+       nulink_usb_init_buffer(handle, 8 + 12 * 1);
+       /* set command ID */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_WRITE_REG);
+       h->cmdidx += 4;
+       /* Count of registers */
+       h->cmdbuf[h->cmdidx] = 1;
+       h->cmdidx += 1;
+       /* Array of bool value (u8ReadOld) */
+       h->cmdbuf[h->cmdidx] = 0xFF;
+       h->cmdidx += 1;
+       /* Array of bool value (u8Verify) */
+       h->cmdbuf[h->cmdidx] = 0x00;
+       h->cmdidx += 1;
+       /* ignore */
+       h->cmdbuf[h->cmdidx] = 0;
+       h->cmdidx += 1;
+       /* u32Addr */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, num);
+       h->cmdidx += 4;
+       /* u32Data */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, 0);
+       h->cmdidx += 4;
+       /* u32Mask */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, 0xFFFFFFFFUL);
+       h->cmdidx += 4;
+
+       int res = nulink_usb_xfer(handle, h->databuf, 4 * 2);
+
+       *val = le_to_h_u32(h->databuf + 4 * 1);
+
+       return res;
+}
+
+static int nulink_usb_write_reg(void *handle, int num, uint32_t val)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       assert(handle);
+
+       nulink_usb_init_buffer(handle, 8 + 12 * 1);
+       /* set command ID */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_WRITE_REG);
+       h->cmdidx += 4;
+       /* Count of registers */
+       h->cmdbuf[h->cmdidx] = 1;
+       h->cmdidx += 1;
+       /* Array of bool value (u8ReadOld) */
+       h->cmdbuf[h->cmdidx] = 0x00;
+       h->cmdidx += 1;
+       /* Array of bool value (u8Verify) */
+       h->cmdbuf[h->cmdidx] = 0x00;
+       h->cmdidx += 1;
+       /* ignore */
+       h->cmdbuf[h->cmdidx] = 0;
+       h->cmdidx += 1;
+       /* u32Addr */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, num);
+       h->cmdidx += 4;
+       /* u32Data */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, val);
+       h->cmdidx += 4;
+       /* u32Mask */
+       h_u32_to_le(h->cmdbuf + h->cmdidx, 0x00000000UL);
+       h->cmdidx += 4;
+
+       return nulink_usb_xfer(handle, h->databuf, 4 * 2);
+}
+
+static int nulink_usb_read_mem8(void *handle, uint32_t addr, uint16_t len,
+               uint8_t *buffer)
+{
+       int res = ERROR_OK;
+       uint32_t offset = 0;
+       uint32_t bytes_remaining = 12;
+       struct nulink_usb_handle_s *h = handle;
+
+       LOG_DEBUG("nulink_usb_read_mem8: addr 0x%08" PRIx32 ", len %" PRId16, addr, len);
+
+       assert(handle);
+
+       /* check whether data is word aligned */
+       if (addr % 4) {
+               uint32_t aligned_addr = addr / 4;
+               aligned_addr = aligned_addr * 4;
+               offset = addr - aligned_addr;
+               LOG_DEBUG("nulink_usb_read_mem8: unaligned address addr 0x%08" PRIx32
+                               "/aligned addr 0x%08" PRIx32 "offset %" PRIu32,
+                               addr, aligned_addr, offset);
+
+               addr = aligned_addr;
+       }
+
+       while (len) {
+               unsigned int count;
+
+               if (len < bytes_remaining)
+                       bytes_remaining = len;
+
+               if (len < 4)
+                       count = 1;
+               else
+                       count = 2;
+
+               nulink_usb_init_buffer(handle, 8 + 12 * count);
+               /* set command ID */
+               h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_WRITE_RAM);
+               h->cmdidx += 4;
+               /* Count of registers */
+               h->cmdbuf[h->cmdidx] = count;
+               h->cmdidx += 1;
+               /* Array of bool value (u8ReadOld) */
+               h->cmdbuf[h->cmdidx] = 0xFF;
+               h->cmdidx += 1;
+               /* Array of bool value (u8Verify) */
+               h->cmdbuf[h->cmdidx] = 0x00;
+               h->cmdidx += 1;
+               /* ignore */
+               h->cmdbuf[h->cmdidx] = 0;
+               h->cmdidx += 1;
+
+               for (unsigned int i = 0; i < count; i++) {
+                       /* u32Addr */
+                       h_u32_to_le(h->cmdbuf + h->cmdidx, addr);
+                       h->cmdidx += 4;
+                       /* u32Data */
+                       h_u32_to_le(h->cmdbuf + h->cmdidx, 0);
+                       h->cmdidx += 4;
+                       /* u32Mask */
+                       h_u32_to_le(h->cmdbuf + h->cmdidx, 0xFFFFFFFFUL);
+                       h->cmdidx += 4;
+                       /* proceed to the next one  */
+                       addr += 4;
+               }
+
+               res = nulink_usb_xfer(handle, h->databuf, 4 * count * 2);
+               if (res != ERROR_OK)
+                       break;
+
+               /* fill in the output buffer */
+               for (unsigned int i = 0; i < count; i++) {
+                       if (i == 0)
+                               memcpy(buffer, h->databuf + 4 + offset, len);
+                       else
+                               memcpy(buffer + 2 * i, h->databuf + 4 * (2 * i + 1), len - 2);
+               }
+
+               if (len >= bytes_remaining)
+                       len -= bytes_remaining;
+       }
+
+       return res;
+}
+
+static int nulink_usb_write_mem8(void *handle, uint32_t addr, uint16_t len,
+               const uint8_t *buffer)
+{
+       int res = ERROR_OK;
+       uint32_t offset = 0;
+       uint32_t bytes_remaining = 12;
+       struct nulink_usb_handle_s *h = handle;
+
+       LOG_DEBUG("nulink_usb_write_mem8: addr 0x%08" PRIx32 ", len %" PRIu16, addr, len);
+
+       assert(handle);
+
+       /* check whether data is word aligned */
+       if (addr % 4) {
+               uint32_t aligned_addr = addr / 4;
+               aligned_addr = aligned_addr * 4;
+               offset = addr - aligned_addr;
+               LOG_DEBUG("nulink_usb_write_mem8: address not aligned. addr(0x%08" PRIx32
+                               ")/aligned_addr(0x%08" PRIx32 ")/offset(%" PRIu32 ")",
+                               addr, aligned_addr, offset);
+
+               addr = aligned_addr;
+       }
+
+       while (len) {
+               unsigned int count;
+
+               if (len < bytes_remaining)
+                       bytes_remaining = len;
+
+               if (len < 4)
+                       count = 1;
+               else
+                       count = 2;
+
+               nulink_usb_init_buffer(handle, 8 + 12 * count);
+               /* set command ID */
+               h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_WRITE_RAM);
+               h->cmdidx += 4;
+               /* Count of registers */
+               h->cmdbuf[h->cmdidx] = count;
+               h->cmdidx += 1;
+               /* Array of bool value (u8ReadOld) */
+               h->cmdbuf[h->cmdidx] = 0x00;
+               h->cmdidx += 1;
+               /* Array of bool value (u8Verify) */
+               h->cmdbuf[h->cmdidx] = 0x00;
+               h->cmdidx += 1;
+               /* ignore */
+               h->cmdbuf[h->cmdidx] = 0;
+               h->cmdidx += 1;
+
+               for (unsigned int i = 0; i < count; i++) {
+                       /* u32Addr */
+                       h_u32_to_le(h->cmdbuf + h->cmdidx, addr);
+                       h->cmdidx += 4;
+                       /* u32Data */
+                       uint32_t u32buffer = buf_get_u32(buffer, 0, len * 8);
+                       u32buffer = (u32buffer << offset * 8);
+                       h_u32_to_le(h->cmdbuf + h->cmdidx, u32buffer);
+                       h->cmdidx += 4;
+                       /* u32Mask */
+                       if (i == 0) {
+                               if (offset == 0) {
+                                       if (len == 1) {
+                                               h_u32_to_le(h->cmdbuf + h->cmdidx, 0xFFFFFF00UL);
+                                               LOG_DEBUG("nulink_usb_write_mem8: count(%u), mask: 0xFFFFFF00", i);
+                                       } else {
+                                               h_u32_to_le(h->cmdbuf + h->cmdidx, 0xFFFF0000UL);
+                                               LOG_DEBUG("nulink_usb_write_mem8: count(%u), mask: 0xFFFF0000", i);
+                                       }
+                               } else {
+                                       if (len == 1) {
+                                               h_u32_to_le(h->cmdbuf + h->cmdidx, 0xFF00FFFFUL);
+                                               LOG_DEBUG("nulink_usb_write_mem8: count(%u), mask: 0xFF00FFFF", i);
+
+                                       } else {
+                                               h_u32_to_le(h->cmdbuf + h->cmdidx, 0x0000FFFFUL);
+                                               LOG_DEBUG("nulink_usb_write_mem8: count(%u), mask: 0x0000FFFF", i);
+                                       }
+                               }
+                       } else {
+                               if (len == 4) {
+                                       h_u32_to_le(h->cmdbuf + h->cmdidx, 0xFFFF0000UL);
+                                       LOG_DEBUG("nulink_usb_write_mem8: count(%u), mask: 0xFFFF0000", i);
+                               } else {
+                                       h_u32_to_le(h->cmdbuf + h->cmdidx, 0x00000000UL);
+                                       LOG_DEBUG("nulink_usb_write_mem8: count(%u), mask: 0x00000000", i);
+                               }
+                       }
+                       h->cmdidx += 4;
+
+                       /* proceed to the next one */
+                       addr += 4;
+                       buffer += 4;
+               }
+
+               res = nulink_usb_xfer(handle, h->databuf, 4 * count * 2);
+               if (res != ERROR_OK)
+                       break;
+
+               if (len >= bytes_remaining)
+                       len -= bytes_remaining;
+       }
+
+       return res;
+}
+
+static int nulink_usb_read_mem32(void *handle, uint32_t addr, uint16_t len,
+               uint8_t *buffer)
+{
+       int res = ERROR_OK;
+       uint32_t bytes_remaining = 12;
+       struct nulink_usb_handle_s *h = handle;
+
+       assert(handle);
+
+       /* data must be a multiple of 4 and word aligned */
+       if (len % 4 || addr % 4) {
+               LOG_ERROR("Invalid data alignment");
+               return ERROR_TARGET_UNALIGNED_ACCESS;
+       }
+
+       while (len) {
+               if (len < bytes_remaining)
+                       bytes_remaining = len;
+
+               unsigned int count = bytes_remaining / 4;
+
+               nulink_usb_init_buffer(handle, 8 + 12 * count);
+               /* set command ID */
+               h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_WRITE_RAM);
+               h->cmdidx += 4;
+               /* Count of registers */
+               h->cmdbuf[h->cmdidx] = count;
+               h->cmdidx += 1;
+               /* Array of bool value (u8ReadOld) */
+               h->cmdbuf[h->cmdidx] = 0xFF;
+               h->cmdidx += 1;
+               /* Array of bool value (u8Verify) */
+               h->cmdbuf[h->cmdidx] = 0x00;
+               h->cmdidx += 1;
+               /* ignore */
+               h->cmdbuf[h->cmdidx] = 0;
+               h->cmdidx += 1;
+
+               for (unsigned int i = 0; i < count; i++) {
+                       /* u32Addr */
+                       h_u32_to_le(h->cmdbuf + h->cmdidx, addr);
+                       h->cmdidx += 4;
+                       /* u32Data */
+                       h_u32_to_le(h->cmdbuf + h->cmdidx, 0);
+                       h->cmdidx += 4;
+                       /* u32Mask */
+                       h_u32_to_le(h->cmdbuf + h->cmdidx, 0xFFFFFFFFUL);
+                       h->cmdidx += 4;
+                       /* proceed to the next one  */
+                       addr += 4;
+               }
+
+               res = nulink_usb_xfer(handle, h->databuf, 4 * count * 2);
+
+               /* fill in the output buffer */
+               for (unsigned int i = 0; i < count; i++) {
+                       memcpy(buffer, h->databuf + 4 * (2 * i + 1), 4);
+                       buffer += 4;
+               }
+
+               if (len >= bytes_remaining)
+                       len -= bytes_remaining;
+               else
+                       len = 0;
+       }
+
+       return res;
+}
+
+static int nulink_usb_write_mem32(void *handle, uint32_t addr, uint16_t len,
+               const uint8_t *buffer)
+{
+       int res = ERROR_OK;
+       uint32_t bytes_remaining = 12;
+       struct nulink_usb_handle_s *h = handle;
+
+       assert(handle);
+
+       /* data must be a multiple of 4 and word aligned */
+       if (len % 4 || addr % 4) {
+               LOG_ERROR("Invalid data alignment");
+               return ERROR_TARGET_UNALIGNED_ACCESS;
+       }
+
+       while (len) {
+               if (len < bytes_remaining)
+                       bytes_remaining = len;
+
+               unsigned int count = bytes_remaining / 4;
+
+               nulink_usb_init_buffer(handle, 8 + 12 * count);
+               /* set command ID */
+               h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_WRITE_RAM);
+               h->cmdidx += 4;
+               /* Count of registers */
+               h->cmdbuf[h->cmdidx] = count;
+               h->cmdidx += 1;
+               /* Array of bool value (u8ReadOld) */
+               h->cmdbuf[h->cmdidx] = 0x00;
+               h->cmdidx += 1;
+               /* Array of bool value (u8Verify) */
+               h->cmdbuf[h->cmdidx] = 0x00;
+               h->cmdidx += 1;
+               /* ignore */
+               h->cmdbuf[h->cmdidx] = 0;
+               h->cmdidx += 1;
+
+               for (unsigned int i = 0; i < count; i++) {
+                       /* u32Addr */
+                       h_u32_to_le(h->cmdbuf + h->cmdidx, addr);
+                       h->cmdidx += 4;
+                       /* u32Data */
+                       uint32_t u32buffer = buf_get_u32(buffer, 0, 32);
+                       h_u32_to_le(h->cmdbuf + h->cmdidx, u32buffer);
+                       h->cmdidx += 4;
+                       /* u32Mask */
+                       h_u32_to_le(h->cmdbuf + h->cmdidx, 0x00000000);
+                       h->cmdidx += 4;
+
+                       /* proceed to the next one */
+                       addr += 4;
+                       buffer += 4;
+               }
+
+               res = nulink_usb_xfer(handle, h->databuf, 4 * count * 2);
+
+               if (len >= bytes_remaining)
+                       len -= bytes_remaining;
+               else
+                       len = 0;
+       }
+
+       return res;
+}
+
+static uint32_t nulink_max_block_size(uint32_t tar_autoincr_block, uint32_t address)
+{
+       uint32_t max_tar_block = (tar_autoincr_block - ((tar_autoincr_block - 1) & address));
+
+       if (max_tar_block == 0)
+               max_tar_block = 4;
+
+       return max_tar_block;
+}
+
+static int nulink_usb_read_mem(void *handle, uint32_t addr, uint32_t size,
+               uint32_t count, uint8_t *buffer)
+{
+       int retval = ERROR_OK;
+       struct nulink_usb_handle_s *h = handle;
+
+       /* calculate byte count */
+       count *= size;
+
+       while (count) {
+               uint32_t bytes_remaining = nulink_max_block_size(h->max_mem_packet, addr);
+
+               if (count < bytes_remaining)
+                       bytes_remaining = count;
+
+               if (bytes_remaining >= 4)
+                       size = 4;
+
+               /* the nulink only supports 8/32bit memory read/writes
+                * honour 32bit, all others will be handled as 8bit access */
+               if (size == 4) {
+                       /* When in jtag mode the nulink uses the auto-increment functinality.
+                        * However it expects us to pass the data correctly, this includes
+                        * alignment and any page boundaries. We already do this as part of the
+                        * adi_v5 implementation, but the nulink is a hla adapter and so this
+                        * needs implementiong manually.
+                        * currently this only affects jtag mode, they do single
+                        * access in SWD mode - but this may change and so we do it for both modes */
+
+                       /* we first need to check for any unaligned bytes */
+                       if (addr % 4) {
+                               uint32_t head_bytes = 4 - (addr % 4);
+                               retval = nulink_usb_read_mem8(handle, addr, head_bytes, buffer);
+                               if (retval != ERROR_OK)
+                                       return retval;
+                               buffer += head_bytes;
+                               addr += head_bytes;
+                               count -= head_bytes;
+                               bytes_remaining -= head_bytes;
+                       }
+
+                       if (bytes_remaining % 4)
+                               retval = nulink_usb_read_mem(handle, addr, 1, bytes_remaining, buffer);
+                       else
+                               retval = nulink_usb_read_mem32(handle, addr, bytes_remaining, buffer);
+               } else {
+                       retval = nulink_usb_read_mem8(handle, addr, bytes_remaining, buffer);
+               }
+
+               if (retval != ERROR_OK)
+                       return retval;
+
+               buffer += bytes_remaining;
+               addr += bytes_remaining;
+               count -= bytes_remaining;
+       }
+
+       return retval;
+}
+
+static int nulink_usb_write_mem(void *handle, uint32_t addr, uint32_t size,
+               uint32_t count, const uint8_t *buffer)
+{
+       int retval = ERROR_OK;
+       struct nulink_usb_handle_s *h = handle;
+
+       if (addr < ARM_SRAM_BASE) {
+               LOG_DEBUG("nulink_usb_write_mem: address below ARM_SRAM_BASE, not supported.\n");
+               return retval;
+       }
+
+       /* calculate byte count */
+       count *= size;
+
+       while (count) {
+               uint32_t bytes_remaining = nulink_max_block_size(h->max_mem_packet, addr);
+
+               if (count < bytes_remaining)
+                       bytes_remaining = count;
+
+               if (bytes_remaining >= 4)
+                       size = 4;
+
+               /* the nulink only supports 8/32bit memory read/writes
+                * honour 32bit, all others will be handled as 8bit access */
+               if (size == 4) {
+                       /* When in jtag mode the nulink uses the auto-increment functinality.
+                        * However it expects us to pass the data correctly, this includes
+                        * alignment and any page boundaries. We already do this as part of the
+                        * adi_v5 implementation, but the nulink is a hla adapter and so this
+                        * needs implementiong manually.
+                        * currently this only affects jtag mode, do single
+                        * access in SWD mode - but this may change and so we do it for both modes */
+
+                       /* we first need to check for any unaligned bytes */
+                       if (addr % 4) {
+                               uint32_t head_bytes = 4 - (addr % 4);
+                               retval = nulink_usb_write_mem8(handle, addr, head_bytes, buffer);
+                               if (retval != ERROR_OK)
+                                       return retval;
+                               buffer += head_bytes;
+                               addr += head_bytes;
+                               count -= head_bytes;
+                               bytes_remaining -= head_bytes;
+                       }
+
+                       if (bytes_remaining % 4)
+                               retval = nulink_usb_write_mem(handle, addr, 1, bytes_remaining, buffer);
+                       else
+                               retval = nulink_usb_write_mem32(handle, addr, bytes_remaining, buffer);
+
+               } else {
+                       retval = nulink_usb_write_mem8(handle, addr, bytes_remaining, buffer);
+               }
+
+               if (retval != ERROR_OK)
+                       return retval;
+
+               buffer += bytes_remaining;
+               addr += bytes_remaining;
+               count -= bytes_remaining;
+       }
+
+       return retval;
+}
+
+static int nulink_usb_override_target(const char *targetname)
+{
+       LOG_DEBUG("nulink_usb_override_target");
+
+       return !strcmp(targetname, "cortex_m");
+}
+
+static int nulink_speed(void *handle, int khz, bool query)
+{
+       struct nulink_usb_handle_s *h = handle;
+       unsigned long max_ice_clock = khz;
+
+       LOG_DEBUG("nulink_speed: query %s", query ? "yes" : "no");
+
+       if (max_ice_clock > 12000)
+               max_ice_clock = 12000;
+       else if ((max_ice_clock == 3 * 512) || (max_ice_clock == 1500))
+               max_ice_clock = 1500;
+       else if (max_ice_clock >= 1000)
+               max_ice_clock = max_ice_clock / 1000 * 1000;
+       else
+               max_ice_clock = max_ice_clock / 100 * 100;
+
+       LOG_DEBUG("Nu-Link nulink_speed: %lu", max_ice_clock);
+
+       if (!query) {
+               nulink_usb_init_buffer(handle, 4 * 6);
+               /* set command ID */
+               h_u32_to_le(h->cmdbuf + h->cmdidx, CMD_SET_CONFIG);
+               h->cmdidx += 4;
+               /* set max SWD clock */
+               h_u32_to_le(h->cmdbuf + h->cmdidx, max_ice_clock);
+               h->cmdidx += 4;
+               /* chip type: NUC_CHIP_TYPE_GENERAL_V6M */
+               h_u32_to_le(h->cmdbuf + h->cmdidx, 0);
+               h->cmdidx += 4;
+               /* IO voltage */
+               h_u32_to_le(h->cmdbuf + h->cmdidx, 5000);
+               h->cmdidx += 4;
+               /* If supply voltage to target or not */
+               h_u32_to_le(h->cmdbuf + h->cmdidx, 0);
+               h->cmdidx += 4;
+               /* USB_FUNC_E: USB_FUNC_HID_BULK */
+               h_u32_to_le(h->cmdbuf + h->cmdidx, 2);
+               h->cmdidx += 4;
+
+               nulink_usb_xfer(handle, h->databuf, 4 * 3);
+
+               LOG_DEBUG("nulink_speed: h->hardware_config(%" PRId16 ")", h->hardware_config);
+               if (h->hardware_config & HARDWARE_CONFIG_NULINKPRO)
+                       LOG_INFO("Nu-Link target_voltage_mv[0](%04" PRIx16 "), target_voltage_mv[1](%04" PRIx16
+                               "), target_voltage_mv[2](%04" PRIx16 "), if_target_power_supplied(%d)",
+                               le_to_h_u16(h->databuf + 4 * 1 + 0),
+                               le_to_h_u16(h->databuf + 4 * 1 + 2),
+                               le_to_h_u16(h->databuf + 4 * 2 + 0),
+                               le_to_h_u16(h->databuf + 4 * 2 + 2) & 1);
+       }
+
+       return max_ice_clock;
+}
+
+static int nulink_usb_close(void *handle)
+{
+       struct nulink_usb_handle_s *h = handle;
+
+       LOG_DEBUG("nulink_usb_close");
+
+       if (h && h->dev_handle)
+               hid_close(h->dev_handle);
+
+       free(h);
+
+       hid_exit();
+
+       return ERROR_OK;
+}
+
+static int nulink_usb_open(struct hl_interface_param_s *param, void **fd)
+{
+       struct hid_device_info *devs, *cur_dev;
+       uint16_t target_vid = 0;
+       uint16_t target_pid = 0;
+       wchar_t *target_serial = NULL;
+
+       LOG_DEBUG("nulink_usb_open");
+
+       if (param->transport != HL_TRANSPORT_SWD)
+               return TARGET_UNKNOWN;
+
+       if (!param->vid[0] && !param->pid[0]) {
+               LOG_ERROR("Missing vid/pid");
+               return ERROR_FAIL;
+       }
+
+       if (hid_init() != 0) {
+               LOG_ERROR("unable to open HIDAPI");
+               return ERROR_FAIL;
+       }
+
+       struct nulink_usb_handle_s *h = calloc(1, sizeof(*h));
+       if (!h) {
+               LOG_ERROR("Out of memory");
+               goto error_open;
+       }
+
+       if (param->serial) {
+               size_t len = mbstowcs(NULL, param->serial, 0);
+
+               target_serial = calloc(len + 1, sizeof(wchar_t));
+               if (!target_serial) {
+                       LOG_ERROR("Out of memory");
+                       goto error_open;
+               }
+
+               if (mbstowcs(target_serial, param->serial, len + 1) == (size_t)(-1)) {
+                       LOG_WARNING("unable to convert serial");
+                       free(target_serial);
+                       target_serial = NULL;
+               }
+       }
+
+       devs = hid_enumerate(0, 0);
+       cur_dev = devs;
+       while (cur_dev) {
+               bool found = false;
+
+               for (unsigned int i = 0; param->vid[i] || param->pid[i]; i++) {
+                       if (param->vid[i] == cur_dev->vendor_id && param->pid[i] == cur_dev->product_id) {
+                               found = true;
+                               break;
+                       }
+               }
+
+               if (found) {
+                       if (!target_serial)
+                               break;
+                       if (cur_dev->serial_number && wcscmp(target_serial, cur_dev->serial_number) == 0)
+                               break;
+               }
+
+               cur_dev = cur_dev->next;
+       }
+       if (cur_dev) {
+               target_vid = cur_dev->vendor_id;
+               target_pid = cur_dev->product_id;
+       }
+
+       hid_free_enumeration(devs);
+
+       if (target_vid == 0 && target_pid == 0) {
+               LOG_ERROR("unable to find Nu-Link");
+               goto error_open;
+       }
+
+       hid_device *dev = hid_open(target_vid, target_pid, target_serial);
+       if (!dev) {
+               LOG_ERROR("unable to open Nu-Link device 0x%" PRIx16 ":0x%" PRIx16, target_vid, target_pid);
+               goto error_open;
+       }
+
+       h->dev_handle = dev;
+       h->usbcmdidx = 0;
+       h->hardware_config = 0;
+       h->max_packet_size = NULINK_HID_MAX_SIZE;
+
+       /* get the device version */
+       int err = nulink_usb_version(h);
+       if (err != ERROR_OK)
+               goto error_open;
+
+       /* SWD clock rate : 1MHz */
+       nulink_speed(h, 1000, false);
+
+       /* get cpuid, so we can determine the max page size
+        * start with a safe default */
+       h->max_mem_packet = (1 << 10);
+
+       LOG_DEBUG("nulink_usb_open: we manually perform nulink_usb_reset");
+       nulink_usb_reset(h);
+
+       *fd = h;
+
+       free(target_serial);
+       return ERROR_OK;
+
+error_open:
+       nulink_usb_close(h);
+       free(target_serial);
+
+       return ERROR_FAIL;
+}
+
+struct hl_layout_api_s nulink_usb_layout_api = {
+       .open = nulink_usb_open,
+       .close = nulink_usb_close,
+       .idcode = nulink_usb_idcode,
+       .state = nulink_usb_state,
+       .reset = nulink_usb_reset,
+       .assert_srst = nulink_usb_assert_srst,
+       .run = nulink_usb_run,
+       .halt = nulink_usb_halt,
+       .step = nulink_usb_step,
+       .read_reg = nulink_usb_read_reg,
+       .write_reg = nulink_usb_write_reg,
+       .read_mem = nulink_usb_read_mem,
+       .write_mem = nulink_usb_write_mem,
+       .write_debug_reg = nulink_usb_write_debug_reg,
+       .override_target = nulink_usb_override_target,
+       .speed = nulink_speed,
+};
index 686e6f5b28dd253ba5aa4883e318158bb888f9d2..cf51a671330889a8274e72c19344393ba5056c75 100644 (file)
@@ -72,6 +72,14 @@ static const struct hl_layout hl_layouts[] = {
         .close = hl_layout_close,
         .api = &icdi_usb_layout_api,
        },
+#endif
+#if BUILD_HLADAPTER_NULINK
+       {
+        .name = "nulink",
+        .open = hl_layout_open,
+        .close = hl_layout_close,
+        .api = &nulink_usb_layout_api,
+       },
 #endif
        {.name = NULL, /* END OF TABLE */ },
 };
index 68052952f4f4521e27ac9d1f7c98f9d970cfb802..e0bbd0fed6feac1bd2242b197f23cf3ae6a69f22 100644 (file)
@@ -31,6 +31,7 @@ struct hl_interface_param_s;
 /** */
 extern struct hl_layout_api_s stlink_usb_layout_api;
 extern struct hl_layout_api_s icdi_usb_layout_api;
+extern struct hl_layout_api_s nulink_usb_layout_api;
 
 /** */
 struct hl_layout_api_s {
diff --git a/tcl/interface/nulink.cfg b/tcl/interface/nulink.cfg
new file mode 100644 (file)
index 0000000..08c8216
--- /dev/null
@@ -0,0 +1,11 @@
+#
+# Nuvoton Nu-Link in-circuit debugger/programmer
+#
+
+adapter driver hla
+hla_layout nulink
+hla_device_desc "Nu-Link"
+hla_vid_pid 0x0416 0x511b 0x0416 0x511c 0x0416 0x511d
+
+# Only swd is supported
+transport select hla_swd

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)