All,

I'm currently trying to port P&E Multilink Universal[1,2]. This is
currently supported by pyocd-pemicro[3] using external dll[4].

I have successfully create a c interface for for the dll and I'm now
trying to integrate this into the openocd code.

I have add it as a hla interface as the dll only supports writing to
memory and I'm having problems getting the system to compile the new
adapter as no hla interfaces have been compiled into the program.

I've attached a patch from the current head(918811) and my code so I
was wondering if anyone could give me any pointers on what I might be
doing wrong.

Many thanks,
Chris

[1]https://www.pemicro.com/products/product_viewDetails.cfm?product_id=15320180&productTab=1
[2]https://www.nxp.com/design/software/development-software/mcuxpresso-software-and-tools-/universal-multilink-development-interface:UMultilink
[3]https://github.com/pyocd/pyocd-pemicro
[4]https://github.com/NXPmicro/pypemicro

diff --git a/.gitignore b/.gitignore
index 955ca3c2e..191c85cd9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,7 @@ src/jtag/drivers/OpenULINK/*.rst
 
 # editor files
 *.swp
+*.log
 
 src/startup.tcl
 startup_tcl.inc
diff --git a/.gitmodules b/.gitmodules
index 23ffa2543..d72a855e9 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -7,3 +7,6 @@
 [submodule "src/jtag/drivers/libjaylink"]
 	path = src/jtag/drivers/libjaylink
 	url = https://repo.or.cz/libjaylink.git
+[submodule "src/jtag/drivers/multilink"]
+	path = src/jtag/drivers/multilink
+	url = https://github.com/NXPmicro/pypemicro.git
diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
new file mode 100644
index 000000000..1a7c3ee11
--- /dev/null
+++ b/.vscode/c_cpp_properties.json
@@ -0,0 +1,19 @@
+{
+    "configurations": [
+        {
+            "name": "Linux",
+            "includePath": [
+                "${default}",
+                "${workspaceFolder}/**"
+            ],
+            "defines": [
+                "HAVE_CONFIG_H"
+            ],
+            "compilerPath": "/usr/lib64/ccache/clang",
+            "cStandard": "c17",
+            "cppStandard": "c++14",
+            "intelliSenseMode": "linux-clang-x64"
+        }
+    ],
+    "version": 4
+}
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 000000000..d6be7a769
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,63 @@
+{
+    "configurations": [
+        {
+            "name": "(gdb) Standard Launch",
+            "type": "cppdbg",
+            "request": "launch",
+            "program": "${workspaceFolder}/src/openocd",
+            "args": [ 
+                "-d4",
+                "--search", "${workspaceFolder}/tcl", 
+                "--file", "interface/multilink.cfg",
+                "--file", "board/nxp_mcimx7ulp-evk.cfg"
+            ],
+            "stopAtEntry": false,
+            "cwd": "${fileDirname}",
+            "environment": [ 
+                {
+                    "name": "LD_LIBRARY_PATH",
+                    "value": "${workspaceFolder}/src/jtag/drivers/multilink/pypemicro/libs/Linux"
+                }
+            ],
+            "externalConsole": false,
+            "MIMode": "gdb",
+            "setupCommands": [
+                {
+                    "description": "Enable pretty-printing for gdb",
+                    "text": "-enable-pretty-printing",
+                    "ignoreFailures": true
+                }
+            ],
+            "preLaunchTask": "build"
+        },
+        {
+            "name": "(gdb) Auto Proble Launch",
+            "type": "cppdbg",
+            "request": "launch",
+            "program": "${workspaceFolder}/src/openocd",
+            "args": [ 
+                "-d4",
+                "--search", "${workspaceFolder}/tcl", 
+                "--file", "interface/multilink.cfg",
+                "--file", "board/nxp_mcimx7ulp-evk_auto.cfg"
+            ],
+            "stopAtEntry": false,
+            "cwd": "${fileDirname}",
+            "environment": [ 
+                {
+                    "name": "LD_LIBRARY_PATH",
+                    "value": "${workspaceFolder}/src/jtag/drivers/multilink/pypemicro/libs/Linux"
+                }
+            ],
+            "externalConsole": false,
+            "MIMode": "gdb",
+            "setupCommands": [
+                {
+                    "description": "Enable pretty-printing for gdb",
+                    "text": "-enable-pretty-printing",
+                    "ignoreFailures": true
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..245a74aa0
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+    "makefile.preConfigureSript": "configure",
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 000000000..efc217247
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,49 @@
+{
+    "version":"2.0.0",
+    "tasks": [
+        {
+            "label": "bootstrap",
+            "command": "./bootstrap",
+            "type": "shell",
+            "args": [ ],
+            "group": "build",
+            "presentation": {
+                "echo": false
+            },
+            "problemMatcher": "$gcc"
+        },
+        {
+            "label": "configure",
+            "command": "./configure",
+            "type": "shell",
+            "args": [ "--enable-multlink", "--enable-verbose", "--enable-verbose-usb-io", "--enable-verbose-usb-comms"],
+            "group": "build",
+            "presentation": {
+                "echo": false
+            },
+            "problemMatcher": "$gcc"
+        },
+        {
+            "label": "build",
+            "command": "make",
+            "type": "shell",
+            "args": [ ],
+            "group": "build",
+            "presentation": {
+                "echo": false
+            },
+            "problemMatcher": "$gcc"
+        },
+        {
+            "label": "clean",
+            "command": "make",
+            "type": "shell",
+            "args": [ "clean" ],
+            "group": "build",
+            "presentation": {
+                "echo": false
+            },
+            "problemMatcher": "$gcc"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/configure.ac b/configure.ac
index a178284ee..0f7c2ce47 100644
--- a/configure.ac
+++ b/configure.ac
@@ -112,6 +112,7 @@ m4_define([USB1_ADAPTERS],
 	[[[ftdi], [MPSSE mode of FTDI based devices], [FTDI]],
 	[[stlink], [ST-Link Programmer], [HLADAPTER_STLINK]],
 	[[ti_icdi], [TI ICDI JTAG Programmer], [HLADAPTER_ICDI]],
+  [[multilink], [P&E Micro Multilink Universal], [HLADAPTER_MULTILINK]],
 	[[ulink], [Keil ULINK JTAG Programmer], [ULINK]],
 	[[usb_blaster_2], [Altera USB-Blaster II Compatible], [USB_BLASTER_2]],
 	[[ft232r], [Bitbang mode of FT232R based devices], [FT232R]],
@@ -123,7 +124,8 @@ m4_define([USB1_ADAPTERS],
 	[[armjtagew], [Olimex ARM-JTAG-EW Programmer], [ARMJTAGEW]],
 	[[rlink], [Raisonance RLink JTAG Programmer], [RLINK]],
 	[[usbprog], [USBProg JTAG Programmer], [USBPROG]],
-	[[aice], [Andes JTAG Programmer], [AICE]]])
+  [[aice], [Andes JTAG Programmer], [AICE]]])
+
 
 m4_define([HIDAPI_ADAPTERS],
 	[[[cmsis_dap], [CMSIS-DAP Compliant Debugger], [CMSIS_DAP_HID]],
@@ -508,7 +510,7 @@ AS_IF([test "x$parport_use_giveio" = "xyes"], [
 ])
 
 AS_IF([test "x$build_jtag_vpi" = "xyes"], [
-  AC_DEFINE([BUILD_JTAG_VPI], [1], [1 if you want JTAG VPI.])
+  AC_DEFINE([JTAG_VPI], [1], [1 if you want JTAG VPI.])
 ], [
   AC_DEFINE([BUILD_JTAG_VPI], [0], [0 if you don't want JTAG VPI.])
 ])
@@ -652,7 +654,8 @@ AS_IF([test "x$enable_linuxgpiod" != "xno"], [
   build_bitbang=yes
 ])
 
-AS_IF([test "x$enable_stlink" != "xno" -o "x$enable_ti_icdi" != "xno" -o "x$enable_nulink" != "xno"], [
+AS_IF([test "x$enable_stlink" != "xno" -o "x$enable_ti_icdi" != "xno" -o "x$enable_nulink" != "xno"
+		-o "x$enable_multilink" != "xno"], [
 	AC_DEFINE([BUILD_HLADAPTER], [1], [1 if you want the High Level JTAG driver.])
 	AM_CONDITIONAL([HLADAPTER], [true])
 ], [
@@ -662,6 +665,7 @@ AS_IF([test "x$enable_stlink" != "xno" -o "x$enable_ti_icdi" != "xno" -o "x$enab
 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"])
+AM_CONDITIONAL([HLADAPTER_MULTILINK], [test "x$enable_multilink" != "xno"])
 
 AS_IF([test "x$enable_jlink" != "xno"], [
   AS_IF([test "x$use_internal_libjaylink" = "xyes"], [
diff --git a/contrib/60-openocd.rules b/contrib/60-openocd.rules
index 4ecb485b1..10fe12e97 100644
--- a/contrib/60-openocd.rules
+++ b/contrib/60-openocd.rules
@@ -160,6 +160,14 @@ ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1061", MODE="660", GROUP="plugdev",
 # Raisonance RLink
 ATTRS{idVendor}=="138e", ATTRS{idProduct}=="9000", MODE="660", GROUP="plugdev", TAG+="uaccess"
 
+# PEmicro Multilink Universal
+ATTR{idVendor}=="15a2", ATTR{idProduct}=="0035", MODE:="0666", GROUP="plugdev", TAG+="uaccess", ATTR{power/control}="on"
+ATTR{idVendor}=="15a2", ATTR{idProduct}=="0042", MODE:="0666", GROUP="plugdev", TAG+="uaccess", ATTR{power/control}="on"
+ATTR{idVendor}=="15a2", ATTR{idProduct}=="0058", MODE:="0666", GROUP="plugdev", TAG+="uaccess", ATTR{power/control}="on"
+ATTR{idVendor}=="15a2", ATTR{idProduct}=="005e", MODE:="0666", GROUP="plugdev", TAG+="uaccess", ATTR{power/control}="on"
+ATTR{idVendor}=="15a2", ATTR{idProduct}=="005f", MODE:="0666", GROUP="plugdev", TAG+="uaccess", ATTR{power/control}="on"
+ATTR{idVendor}=="1357", MODE:="0666", GROUP="plugdev", TAG+="uaccess", ATTR{power/control}="on"
+
 # Debug Board for Neo1973
 ATTRS{idVendor}=="1457", ATTRS{idProduct}=="5118", MODE="660", GROUP="plugdev", TAG+="uaccess"
 
diff --git a/src/jtag/drivers/Makefile.am b/src/jtag/drivers/Makefile.am
index da60f366e..5c5491bd4 100644
--- a/src/jtag/drivers/Makefile.am
+++ b/src/jtag/drivers/Makefile.am
@@ -8,6 +8,7 @@ noinst_LTLIBRARIES += %D%/libocdjtagdrivers.la
 %C%_libocdjtagdrivers_la_CPPFLAGS = $(AM_CPPFLAGS)
 
 ULINK_FIRMWARE = %D%/OpenULINK
+MULTILINK_BLOB = %D%/multilink/pypemicro/libs/
 
 EXTRA_DIST += $(ULINK_FIRMWARE) \
 	%D%/usb_blaster/README.CheapClone \
@@ -114,7 +115,6 @@ if ULINK
 DRIVERFILES += %D%/ulink.c
 ulinkdir = $(pkgdatadir)/OpenULINK
 dist_ulink_DATA = $(ULINK_FIRMWARE)/ulink_firmware.hex
-%C%_libocdjtagdrivers_la_LIBADD += -lm
 endif
 if VSLLINK
 DRIVERFILES += %D%/versaloon/usbtoxxx/usbtogpio.c
@@ -143,6 +143,12 @@ endif
 if HLADAPTER_NULINK
 DRIVERFILES += %D%/nulink_usb.c
 endif
+if HLADAPTER_MULTILINK
+DRIVERFILES += %D%/multilink.c
+multilinkdir = $(pkgdatadir)/Multilink
+dist_multilink_DATA = ${MULTILINK_BLOB}/Linux/unitacmp-64.so
+%C%_libocdjtagdrivers_la_LIBADD += -lm
+endif
 if RSHIM
 DRIVERFILES += %D%/rshim.c
 endif
@@ -193,6 +199,7 @@ DRIVERHEADERS = \
 	%D%/cmsis_dap.h \
 	%D%/minidriver_imp.h \
 	%D%/mpsse.h \
+	%D%/multilink.h \
 	%D%/rlink.h \
 	%D%/rlink_dtc_cmd.h \
 	%D%/rlink_ep1_cmd.h \
diff --git a/src/jtag/drivers/arm-jtag-ew.c b/src/jtag/drivers/arm-jtag-ew.c
index 5b5a9669e..cec134ca6 100644
--- a/src/jtag/drivers/arm-jtag-ew.c
+++ b/src/jtag/drivers/arm-jtag-ew.c
@@ -793,7 +793,7 @@ static void armjtagew_debug_buffer(uint8_t *buffer, int length)
 	int j;
 
 	for (i = 0; i < length; i += BYTES_PER_LINE) {
-		snprintf(line, 5, "%04x", i);
+		snprintf(line, 9, "%04x", i);
 		for (j = i; j < i + BYTES_PER_LINE && j < length; j++) {
 			snprintf(s, 4, " %02x", buffer[j]);
 			strcat(line, s);
diff --git a/src/jtag/drivers/multilink b/src/jtag/drivers/multilink
new file mode 160000
index 000000000..fb277592d
--- /dev/null
+++ b/src/jtag/drivers/multilink
@@ -0,0 +1 @@
+Subproject commit fb277592d84dd103d3ab705cc259b8866d6160c6
diff --git a/src/jtag/drivers/multilink.c b/src/jtag/drivers/multilink.c
new file mode 100644
index 000000000..378dc6e0e
--- /dev/null
+++ b/src/jtag/drivers/multilink.c
@@ -0,0 +1,1902 @@
+/**
+ * @file multilink.c
+ * @author Christopher West (cw...@thedigitaledge.co.uk)
+ * @brief Interface to the P&E Micro Multilink Universal devices to OpenOCD
+ * @version 0.1
+ * @date 2021-09-20
+ * 
+ * @copyright Copyright (c) 2021, BSD-3-Clause
+ * 
+ * The following code uses the pypemicro DLL to interface with the Multilink 
+ * universal hardare and as such has only been tested on the Multilink/FX and 
+ * Cyclone/FX probes using NXP ARM microcontrollers.
+ * For more information see: https://github.com/NXPmicro/pypemicro
+ * 
+ * This device_state supports the following communications protocol:
+ *  - JTAG
+ *  - SWD
+ * 
+ * This device_state supports the following capabilites:
+ *  - SWO
+ *  - Managed AP selection
+ *  - Banked DP registers
+ *  - Manages DP bank selection
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#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 <helper/replacements.h>
+
+#include <target/target.h>
+#include <target/cortex_m.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <assert.h>
+
+#include "libusb_helper.h"
+#include "multilink.h"
+
+#define MAX_FILE_PATH_SIZE 4096
+
+/**
+ * @brief 
+ * 
+ */
+struct ml_handle_s {
+	//
+	struct libusb_device_handle *usb_dev;
+	uint16_t max_packet_size;
+	uint8_t usbcmdidx;
+	uint8_t cmdidx;
+	uint8_t cmdsize;
+	uint8_t cmdbuf[UNIVERSAL_HID_MAX_SIZE + 1];
+	uint8_t tempbuf[UNIVERSAL_HID_MAX_SIZE];
+	uint8_t databuf[UNIVERSAL_HID_MAX_SIZE];
+	uint32_t max_mem_packet;
+	uint16_t hardware_config;
+	//
+	int (*xfer)(void *handle, uint8_t *buf, int size);
+	void (*init_buffer)(void *handle, uint32_t size);
+	/// Multilink Universal
+	struct ml_device_state_s *device_state;
+	struct ml_device_api_s *device_api;
+};
+
+struct ml_handle_s universal_usb_handle;
+
+/****************************************************************************
+ * P&E Multilink - Setup DLL symbol call wrappers							*
+ ****************************************************************************/
+
+/**
+ * @brief 
+ * 
+ * @param usb_handle 
+ * @param dll_file 
+ * @return int 
+ */
+int ml_universal_load_dll(struct ml_device_state_s *device_state, 
+		struct ml_device_api_s *device_api, char *dll_file)
+{
+	void *universal_handle = NULL;
+	char *file_name;
+	int error_msg = ERROR_OK;
+
+	LOG_DEBUG_IO("function called");
+	
+	/// Check DLL file name
+	file_name = calloc(1, MAX_FILE_PATH_SIZE);
+	if (file_name == NULL) {
+		LOG_ERROR("ml_universal_load_dll() failed: Couldn't allocate memory "
+				"for file name");
+		error_msg = ERROR_FAIL;
+	} 
+	//
+	if (error_msg == ERROR_OK) {
+		if (dll_file == NULL) {
+			char default_name[] = "unitacmp-64.so\0";
+			strcpy(file_name, default_name);
+		} else {
+			// 
+			if (strlen(dll_file) <= MAX_FILE_PATH_SIZE) {
+				strcpy(file_name, dll_file);
+			} else {
+				LOG_ERROR("ml_universal_load_dll() failed: Path length to long");
+				error_msg = ERROR_FAIL;
+			}
+		}
+		/// Open Dynamicly Linked Library
+		if (error_msg == ERROR_OK) {
+			universal_handle = dlopen((const char*) file_name, RTLD_LAZY);
+			if (!universal_handle) {
+				device_state->dll_handle = NULL;
+				error_msg = ERROR_FAIL;
+			}
+		}
+	}
+
+	if (error_msg == ERROR_OK) {
+		/// Assign DLL function to functions pointers
+		device_api->command_and_control = (bool (*)(unsigned long, bool, 
+				unsigned long, unsigned long, unsigned long, void*, void*))
+				dlsym(universal_handle, "pe_special_features");
+
+		device_api->open_port = (bool (*)(unsigned long, unsigned long))
+				dlsym(universal_handle, "open_port");
+
+		device_api->open_port_by_identifier = (bool (*)(char*))
+				dlsym(universal_handle, "open_port_by_identifier");
+
+		device_api->close_port = (void (*)(void))
+				dlsym(universal_handle, "close_port");
+
+		device_api->reenumerate_all_port_types = (bool (*)(void))
+				dlsym(universal_handle, "reenumerate_all_port_types");
+
+		device_api->get_enumerated_number_of_ports = (unsigned long (*)
+				(unsigned long))
+				dlsym(universal_handle, "get_enumerated_number_of_ports");
+
+		device_api->get_port_descriptor = (char* (*)(unsigned long, 
+				unsigned long))
+				dlsym(universal_handle, "get_port_descriptor");
+
+		device_api->get_port_descriptor_short = (char* (*)(unsigned long, 
+				unsigned long))
+				dlsym(universal_handle, "get_port_descriptor_short");
+
+		device_api->reset_hardware_interface = (void (*)(void)) 
+				dlsym(universal_handle, "reset_hardware_interface");
+
+		device_api->check_critical_error = (unsigned char (*)(void))
+				dlsym(universal_handle, "check_critical_error");
+
+		device_api->version = (char* (*)(void))
+				dlsym(universal_handle, "version");
+
+		device_api->get_dll_version = (unsigned short (*)(void))
+				dlsym(universal_handle, "get_dll_version");
+
+		device_api->get_cable_version = (unsigned short (*)(void))
+				dlsym(universal_handle, "get_cable_version");
+
+		device_api->set_debug_shift_frequency = (void (*)(unsigned long))
+				dlsym(universal_handle, "set_debug_shift_frequency");
+
+		device_api->set_reset_delay_in_ms = (void (*)(unsigned long))
+				dlsym(universal_handle, "set_reset_delay_in_ms");
+
+		device_api->target_reset = (bool (*)(void)) 
+				dlsym(universal_handle, "target_reset");
+
+		device_api->target_check_if_halted = (bool (*)(void))
+				dlsym(universal_handle, "target_check_if_halted");
+
+		device_api->target_halt = (bool (*)(void))
+				dlsym(universal_handle, "target_halt");
+
+		device_api->target_resume = (bool (*)(void)) 
+				dlsym(universal_handle, "target_resume");
+
+		device_api->target_step = (bool (*)(void)) 
+				dlsym(universal_handle, "target_step");
+
+		device_api->set_reset_pin_state = (void (*)(unsigned char))
+				dlsym(universal_handle, "set_reset_pin_state");
+
+		device_api->open_debug_file = (void (*)(char*))
+				dlsym(universal_handle, "open_debug_file");
+
+		device_api->close_debug_file = (void (*)(char*))
+				dlsym(universal_handle, "close_debug_file");
+
+		device_api->read_32bit_value = (unsigned long (*)(unsigned long,
+				unsigned long, ml_mem_result*)) 
+				dlsym(universal_handle, "read_32bit_value");
+
+		device_api->write_32bit_value = (void (*)(unsigned long, unsigned long, 
+				unsigned long, ml_mem_result*))
+				dlsym(universal_handle, "write_32bit_value");
+
+		device_api->read_16bit_value = (unsigned short (*)(unsigned long,
+				unsigned long, ml_mem_result*))
+				dlsym(universal_handle, "read_16bit_value");
+
+		device_api->write_16bit_value = (void (*)(unsigned long, unsigned long, 
+				unsigned long, ml_mem_result*))
+				dlsym(universal_handle, "write_16bit_value");
+
+		device_api->read_8bit_value = (unsigned char (*)(unsigned long,
+				unsigned long, ml_mem_result*))
+				dlsym(universal_handle, "read_8bit_value");
+
+		device_api->write_8bit_value = (void (*)(unsigned long, unsigned long,
+				unsigned long, ml_mem_result*))
+				dlsym(universal_handle, "write_8bit_value");
+
+		device_api->get_block = (bool (*)(unsigned long, unsigned long,
+				unsigned long, unsigned long, unsigned char*, unsigned char*))
+				dlsym(universal_handle, "get_block");
+
+		device_api->put_block = (bool (*)(unsigned long, unsigned long,
+				unsigned long, unsigned long, unsigned char*, unsigned char*))
+				dlsym(universal_handle, "put_block");
+
+		device_api->load_bin_file = (bool (*)(char*, unsigned long))
+				dlsym(universal_handle, "load_bin_file");
+
+		device_api->load_srec_file = (bool (*)(char*, unsigned long))
+				dlsym(universal_handle, "load_srec_file");
+
+		device_api->get_mcu_register = (bool (*)(unsigned long, unsigned long, 
+				unsigned long*))
+				dlsym(universal_handle, "get_mcu_register");
+
+		device_api->set_mcu_register = (bool (*)(unsigned long, unsigned long, 
+				unsigned long))
+				dlsym(universal_handle, "set_mcu_register");
+
+		/// Make sure that the library has loaded correctly
+		char current_dir[] = "/tmp\0";
+		bool set_dir = device_api->command_and_control(SET_DEFAULT_APP_FILES_DIR,
+				true, 0, 0, 0, current_dir, NULL);
+		if (set_dir == false) {
+			device_state->dll_handle = NULL;
+			LOG_ERROR("ml_universal_load_dll() failed: "
+					"Couldn't set source directory");
+			error_msg = ERROR_JTAG_INIT_FAILED;
+		}
+
+		if (error_msg == ERROR_OK) {
+			/// Save universal handle
+			device_state->dll_handle = universal_handle;
+		}
+	}
+
+	return 1;
+}
+
+/**
+ * @brief Unload the DLL cleanly
+ * 
+ * @return int 
+ */
+int ml_universal_unload_dll(struct ml_device_state_s *device_state)
+{
+	LOG_DEBUG_IO("function called");
+	/// Cleanly close DLL
+	return dlclose(device_state->dll_handle);
+}
+
+/****************************************************************************
+ * P&E Multilink - DLL symbol function call wrappers						*
+ ****************************************************************************/
+
+/**
+ * @brief Powers up the Multilink Universal
+ * 
+ * @return int 
+ */
+static int universal_power_on(struct ml_handle_s* multilink)
+{
+	LOG_DEBUG_IO("function called");
+
+	struct ml_device_api_s* device_api = multilink->device_api;
+	struct ml_device_state_s* device_state = multilink->device_state;
+
+	if (device_state->ref_count == 0) {
+		LOG_ERROR("universal_power_on() failed: No port connected");
+		return ERROR_JTAG_DEVICE_ERROR;
+	}
+
+	// Turn on device_state
+	bool device_on = device_api->command_and_control(PWR_TURN_POWER_ON, true,
+			0, 0, 0, NULL, NULL);
+	if (device_on == false) {
+		LOG_ERROR("universal_power_on() failed: Couldn't turn ON device_state");
+		return ERROR_JTAG_DEVICE_ERROR;
+	}
+
+	return ERROR_OK;
+}
+
+/**
+ * @brief Powers off the Multilink Universal
+ * 
+ * @return int 
+ */
+static int universal_power_off(struct ml_handle_s* multilink)
+{
+	LOG_DEBUG_IO("function called");
+
+	struct ml_device_state_s *device_state = multilink->device_state;
+	struct ml_device_api_s *device_api = multilink->device_api;
+ 
+	if (device_state->ref_count == 0) {
+		LOG_ERROR("universal_power_off() failed: No port connected");
+		return ERROR_JTAG_DEVICE_ERROR;
+	}
+
+	// Turn on device_state
+	bool device_on = device_api->command_and_control(PWR_TURN_POWER_OFF, true, 
+			0, 0, 0, NULL, NULL);
+	if (device_on == false) {
+		LOG_ERROR("universal_power_off() failed: Couldn't turn OFF device_state");
+		return ERROR_JTAG_DEVICE_ERROR;
+	}
+
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param search_string 
+ */
+static void universal_list_devices(struct ml_handle_s* multilink,
+		char* search_string)
+{
+	LOG_DEBUG_IO("function called");
+
+	/// Create memory for device_state list
+	unsigned int list_size = 100000;
+	char* device_list = (char*) calloc(list_size, sizeof(char));
+	if (device_list == NULL) {
+		LOG_ERROR("universal_list_devices() failed :Out of memory");
+	} 
+
+	/// Write device_state list to memory
+	struct ml_device_api_s* device_api = multilink->device_api;
+	bool devices = device_api->command_and_control(GENERIC_GET_DEVICE_LIST,
+			true, list_size, 0, 0, device_list, NULL);
+	if (devices == false) {
+		LOG_ERROR("universal_list_devices() failed: call to get device list ");
+	} else {
+		// Display device_state list
+		if (device_list[0] != '\0') {
+			LOG_INFO("Multilink attached devices:\n%s", device_list);
+		} else {
+			LOG_INFO("No attached Multilink devices found");
+		}
+	}
+	
+	/// Free memory for device_state list
+	if (device_list != NULL) {
+		free(device_list);
+	}
+}
+
+/**
+ * @brief Prints out the Universal driver DLL version string to the INFO stream
+ * 
+ */
+static void universal_display_cable_version(struct ml_handle_s* multilink) 
+{
+	LOG_DEBUG_IO("function called");
+
+	struct ml_device_api_s* device_api = multilink->device_api;
+	short cable_version = device_api->get_cable_version();
+
+	LOG_INFO("Cable version: %d", cable_version);
+}
+
+/**
+ * @brief Prints out the Universal driver DLL version string to the INFO stream
+ * 
+ */
+static void universal_display_version(struct ml_handle_s* multilink) 
+{
+	LOG_DEBUG_IO("function called");
+
+	struct ml_device_api_s* device_api = multilink->device_api;
+	char* driver_info = device_api->version();
+
+	LOG_INFO("Multilink driver version: %s", driver_info);
+}
+
+/**
+ * @brief Prints out the Universal firmwire version string to the INFO stream
+ * 
+ * @param short_info Set if the short string should be printed
+ */
+static void universal_display_devices(struct ml_handle_s* multilink, 
+		bool short_info) 
+{
+	LOG_DEBUG_IO("function called: display short=%s", 
+			short_info ? "true" : "false");
+
+	struct ml_device_api_s* device_api = multilink->device_api;
+	int port_count = device_api->get_enumerated_number_of_ports(AUTODETECT); 
+	
+	if (port_count == 0) {
+		LOG_ERROR("No Multilink Universal device_state found");
+	} else {
+		LOG_DEBUG("Multilink Universal no ports : No. ports = %d", port_count);
+		for (int x = 1; x <= port_count; x++) {
+			if (short_info) {
+				char* port_info = device_api->get_port_descriptor_short(AUTODETECT, x);
+				LOG_INFO("%s", port_info);
+			} 
+			else {
+				char* port_info = device_api->get_port_descriptor(AUTODETECT, x);
+				LOG_INFO("%s", port_info);
+			}
+		}
+	}
+}
+
+/**
+ * @brief 
+ * 
+ * @return int 
+ */
+static int universal_enable_debug(struct ml_handle_s* multilink)
+{
+	LOG_DEBUG_IO("function called");
+
+	// Turn on debugging mode
+	struct ml_device_api_s* device_api = multilink->device_api;
+	bool debug_mode = device_api->command_and_control(ARM_ENABLE_DEBUG_MODULE,
+			true, 0, 0, 0, NULL, NULL);
+
+	if (debug_mode == false) {
+		unsigned char error_state = device_api->check_critical_error();
+		if (error_state != 0x00) {
+			LOG_ERROR("universal_enable_debug() failed: %d", error_state);
+			return ERROR_JTAG_INIT_FAILED;
+		}
+	}
+
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @return int 
+ */
+static int universal_enable_jtag(struct ml_handle_s* multilink)
+{
+	struct ml_device_api_s* device_api = multilink->device_api;
+	struct ml_device_state_s* device_state = multilink->device_state;
+
+	if (device_state->ref_count == 0) {
+		LOG_ERROR("universal_enable_swd() failed: No port connected");
+		return ERROR_JTAG_DEVICE_ERROR;
+	}
+
+	LOG_DEBUG_IO("function called");
+
+	// Set device_state to JTAG mode
+	bool comms_mode = device_api->command_and_control(ARM_SET_COMMUNICATIONS_MODE, 
+			true, ARM_SET_DEBUG_COMM_JTAG, 0, 0, NULL, NULL);
+	
+	if (comms_mode == false) {
+		LOG_ERROR("universal_enable_jtag() failed: Set communications mode");
+		return ERROR_JTAG_INIT_FAILED;
+	}
+
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @return int 
+ */
+static int universal_enable_swd(struct ml_handle_s* multilink)
+{
+	struct ml_device_api_s* device_api = multilink->device_api;
+	struct ml_device_state_s* device_state = multilink->device_state;
+
+	if (device_state->ref_count == 0) {
+		LOG_ERROR("universal_enable_swd() failed: No port connected");
+		return ERROR_JTAG_DEVICE_ERROR;
+	}
+
+	LOG_DEBUG_IO("function called");
+
+	// Set device_state to JTAG mode
+	bool comms_mode = device_api->command_and_control(ARM_SET_COMMUNICATIONS_MODE, 
+			true, ARM_SET_DEBUG_COMM_SWD, 0, 0, NULL, NULL);
+	
+	if (comms_mode == false) {
+		LOG_ERROR("universal_enable_swd() failed: Set communications mode");
+		return ERROR_JTAG_INIT_FAILED;
+	}
+
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param port_name 
+ * @return int 
+ */
+static int universal_open_port(struct ml_handle_s* multilink, char* port_name) 
+{
+	LOG_DEBUG_IO("function called: port name=%s", port_name);
+
+	struct ml_device_api_s* device_api = multilink->device_api;
+	bool port_open = device_api->open_port_by_identifier(port_name);
+	
+	if (port_open == false) {
+		LOG_ERROR("universal_open_port() failed: Couldn't open device_state");
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+
+/**
+ * @brief Flush any pending command to the device_state under test
+ * 
+ * @return int 
+ */
+static int universal_flush(struct ml_handle_s* multilink)
+{ 
+	LOG_DEBUG_IO("function called");
+
+	struct ml_device_api_s* device_api = multilink->device_api;
+	struct ml_device_state_s* device_state = multilink->device_state;
+
+	// Check there are valid connection
+	if (device_state->ref_count == 0) {
+		LOG_ERROR("universal_flush() failed: No configured devices");
+		return ERROR_JTAG_DEVICE_ERROR;
+	}
+
+	// Flush data
+	bool comms_mode = device_api->command_and_control(ARM_FLUSH_ANY_QUEUED_DATA, true, 
+			ARM_SET_DEBUG_COMM_JTAG, 0, 0, NULL, NULL);
+	if (comms_mode == false) {
+		LOG_ERROR("universal_flush() failed: Flush queued data");
+		return ERROR_JTAG_INIT_FAILED;
+	}
+	
+	LOG_DEBUG("All queued data has been flushed data");
+	return ERROR_OK;
+}
+
+/**
+ * @brief Change the JTAG clock speed
+ * 
+ * @param handle 
+ * @param khz 
+ * @param query 
+ * @return int 
+ */
+static int universal_usb_debug_speed(void *handle, int khz, bool query)
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+	LOG_DEBUG_IO("function called: kHz=%d", khz);
+
+	struct ml_device_api_s* device_api = multilink->device_api;
+
+	//ml_set_debug_shift_frequency(speed);
+	unsigned char error_state = device_api->check_critical_error();
+	if (error_state != 0x00) {
+		LOG_ERROR("universal_usb_debug_speed() failed: %d", error_state);
+		return ERROR_FAIL;
+	} 
+
+	return ERROR_OK;
+}
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Create debug a file using the provided file name
+ * 
+ * This allows the BDM communication traffic to be recorded to an encrypted 
+ * debug file. This is for P&E's use in diagnosing problems in the field. 
+ * This call will start recording all commands and target responses of debug 
+ * commands to a specified file. The filename should be specified with a full
+ * path.
+ * 
+ * @param debug_file debug file name
+ * @return int 
+ */
+static int universal_open_debug_file(struct ml_handle_s* multilink,
+		char* debug_file) 
+{
+
+	if (debug_file == NULL) {
+		LOG_ERROR("universal_open_debug_file() failed: Null file name");
+		return ERROR_FAIL;
+	}
+
+	LOG_DEBUG_IO("function called: File name=%s", debug_file);
+	struct ml_device_api_s* device_api = multilink->device_api;
+
+	device_api->open_debug_file(debug_file);
+
+	unsigned char error_state = device_api->check_critical_error();
+	if (error_state != 0x00) {
+		LOG_ERROR("universal_open_debug_file() failed: %d", error_state);
+		return ERROR_FAIL;
+	} 
+
+	return ERROR_OK;
+}
+
+/**
+ * @brief Close the debug file using the provided file name
+ * 
+ * @param debug_file debug file name
+ * @return int 
+ */
+static int universal_close_debug_file(struct ml_handle_s* multilink,
+		char* debug_file) 
+{
+	if (debug_file == NULL) {
+		LOG_ERROR("universal_open_debug_file() failed: Null file name");
+		return ERROR_FAIL;
+	}
+
+	LOG_DEBUG_IO("function called: File name=%s", debug_file);
+
+	struct ml_device_api_s* device_api = multilink->device_api;
+	unsigned char error_state = device_api->check_critical_error();
+	if (error_state != 0x00) {
+		LOG_ERROR("universal_close_debug_file() failed: %d", error_state);
+		return ERROR_FAIL;
+	} 
+
+	return ERROR_OK;
+}
+#endif 
+
+/**
+ * @brief Reset the JTAG TAP interface
+ * 
+ * This resets the JTAG TAP hardware interface only, not the MCU target.
+ * 
+ * @return int JTAG interface state
+ */
+static int universal_reset_interface(struct ml_handle_s* multilink)
+{
+	LOG_DEBUG_IO("function called");
+
+	struct ml_device_api_s* device_api = multilink->device_api;
+
+	device_api->reset_hardware_interface();
+
+	// Check critical error as there is no return code from the above function
+	unsigned char probe_error = device_api->check_critical_error();
+	if (probe_error != 0x00) {
+		LOG_ERROR("universal_reset_interface() failed: "
+				"Cannot reset TAP JTAG = '%d'", probe_error);
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Reset the MCU target
+ * 
+ * This resets the MCU target but not the TAP JTAG hardware interface.
+ * 
+ * @return int MCU target state
+ */
+static int universal_reset_target(struct ml_handle_s* multilink) 
+{
+	LOG_DEBUG_IO("function called");
+
+	struct ml_device_api_s* device_api = multilink->device_api;
+	bool reset_state = device_api->target_reset();
+
+	if (reset_state == false) {
+		LOG_ERROR("universal_reset_target() failed: Cannot reset MCU");
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+#endif 
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief 
+ * 
+ * @param trst Resets the JTAG TAP controller
+ * @param srst Resets the CPU core reset peripheral connections
+ */
+static int universal_reset(void *handle, int trst, int srst)
+{
+	LOG_DEBUG_IO("function called: TRST=%i, SRST=%i", trst, srst);
+
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+
+	// Reset TAP device_state
+	if (trst == 1) {
+		int tap_state = universal_reset_interface(multilink);
+		if (tap_state != ERROR_OK) {
+			return tap_state;
+		}
+	}
+
+	// Reset target CPU
+	if (srst == 1) {
+		int rst_state = universal_reset_target(multilink);
+		if (rst_state != ERROR_OK) {
+			return rst_state;
+		}
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+/**
+ * @brief Check to see if the CPU is running.
+ * 
+ * @return int 1 = RUNNING, 0 = HALTED, -1 = ERROR
+ */
+static int universal_target_running(struct ml_handle_s* multilink)
+{
+	struct ml_device_api_s* device_api = multilink->device_api;
+	struct ml_device_state_s* device_state = multilink->device_state;
+
+	if (device_state->ref_count == 0) {
+		LOG_ERROR("universal_target_running() failed: No port connected");
+		return ERROR_FAIL;
+	}
+
+	/// Return running state
+	bool running = device_api->target_check_if_halted();
+	//
+	return (running == true)? 1 : 0;
+}
+
+/**
+ * @brief Check to see if the CPU is halted in debug mode.
+ * 
+ * @return int 1 = HALTED, 0 <= ERROR
+ */
+static int universal_halt_target(struct ml_handle_s* multilink)
+{
+	struct ml_device_api_s* device_api = multilink->device_api;
+	struct ml_device_state_s* device_state = multilink->device_state;
+
+	if (device_state->ref_count == 0) {
+		LOG_ERROR("universal_halt_target() failed: No port connected");
+		return ERROR_FAIL;
+	}
+
+	bool halt_state = device_api->target_halt();
+
+	if (halt_state == false) {
+		LOG_ERROR("universal_halt_target() failed: Cannot halt target");
+		return ERROR_FAIL;
+	}
+
+	return 1;
+}
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief 
+ * 
+ * @return int 
+ */
+static int universal_resume_target(universal_usb_handle* multilink)
+{
+	struct ml_device_api_s* device_api = multilink->usb_device;
+	bool resume_state = device_api->target_resume();
+
+	if (resume_state == false) {
+		LOG_ERROR("universal_resume_target() failed: Cannot resume target");
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Make the CPU execute a single assembly instruction
+ * 
+ * Causes the CPU to execute a single assembly (machine) instruction at the 
+ * current program counter.
+ * 
+ * @return int 
+ */
+static int universal_step_target(universal_usb_handle* multilink)
+{
+	struct ml_device_api_s* device_api = multilink->usb_device;
+	bool step_state = device_api->target_step();
+
+	if (step_state == false) {
+		LOG_ERROR("universal_step_target() failed: Cannot step target");
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Triggers the MCU's reset line
+ * 
+ * @param reset_target 1 to reset MCU, 0 do nothing
+ */ 
+static int universal_trigger_reset_line(universal_usb_handle* multilink,
+		const bool reset_target)
+{
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	// Device reset is active low
+	unsigned char line_level = (reset_target == true) ? 0 : 1;
+	device_api->set_reset_pin_state(line_level);
+
+	unsigned char line_state = device_api->check_critical_error();
+	if (line_state != 0x00) {
+		LOG_ERROR("universal_trigger_reset_line() failed: %d", 
+				line_state);
+		return ERROR_FAIL;
+	} 
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Set the time for the reset line to say low in milliseconds
+ * 
+ * @param delay_ms delay time in milliseconds
+ * @return int 
+ */
+static int universal_set_target_reset_delay(universal_usb_handle* multilink,
+		const unsigned long delay_ms) 
+{
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	device_api->set_reset_delay_in_ms(delay_ms);
+
+	unsigned char line_state = device_api->check_critical_error();
+	if (line_state != 0x00) {
+		LOG_ERROR("universal_set_target_reset_delay() failed: %d", line_state);
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Reads 32 bits of data.
+ * 
+ * Reads 32 bits of data from a specified memory address location. 
+ * 
+ * To read more data than 32 bits use the following:
+ * - <universal_read_block>"()" for multiple blocks of data
+ * 
+ * @param memory_address address to start the 32 bits read from
+ * @param read_memory_data data pointer for the read data
+ * @return int interface error code
+ */
+static int universal_read_32bit(universal_usb_handle* multilink,
+		unsigned long memory_address, unsigned short* read_memory_data)
+{
+	unsigned long memory_access_tag = 0;
+	mem_result read_result;
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	unsigned short read_value = device_api->read_32bit_value(memory_access_tag, 
+			memory_address, &read_result);
+
+	if (read_result != MEM_OK) {
+		LOG_ERROR("universal_read_32bit() failed: error code = '%d'", 
+				read_result);
+		return ERROR_FAIL;
+	}
+
+	*read_memory_data = read_value;
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Write 32 bits of data.
+ *
+ * Writes 32 bits of data to a specified memory address location. 
+ * 
+ * To write more data than 32 bits use the following:
+ * - <universal_write_block>"()" for multiple blocks of data
+ * 
+ * @param memory_address address to start the 32 bits writing to
+ * @param read_memory_data data pointer for the write data
+ */
+static int universal_write_32bit(universal_usb_handle* multilink,
+		unsigned long memory_address, unsigned long memory_data)
+{
+	unsigned long memory_access_tag = 0;
+	mem_result write_result;
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	device_api->write_32bit_value(memory_access_tag, memory_address, memory_data, 
+			&write_result);
+
+	if (write_result != MEM_OK) {
+		LOG_ERROR("universal_write_32bit() failed: error='%d'", write_result);
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Reads 16 bits of data.
+ * 
+ * Reads 16 bits of data from a specified memory address location. 
+ * 
+ * To read more data than 16 bits use the following:
+ * - <universal_read_32bit>"()" for 32 bits of data
+ * - <universal_read_block>"()" for multiple blocks of data
+ * 
+ * @param memory_address address to start the 16 bits read from
+ * @param read_memory_data data pointer for the read data
+ * @return int 
+ */
+static int universal_read_16bit(universal_usb_handle* multilink,
+		unsigned long memory_address, unsigned short* read_memory_data)
+{
+	unsigned long memory_access_tag = 0;
+	mem_result read_result;
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	unsigned short read_value = device_api->read_32bit_value(memory_access_tag, 
+		memory_address, &read_result);
+
+	if (read_result != MEM_OK) {
+		LOG_ERROR("universal_read_16bit() failed: error='%d'", read_result);
+		return ERROR_FAIL;
+	}
+
+	*read_memory_data = read_value;
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Write 16 bits of data.
+ *
+ * Writes 16 bits of data to a specified memory address location. 
+ * 
+ * To write more data than 8 bits use the following:
+ * - <universal_write_32bit>"()" for 32 bits of data
+ * - <universal_write_block>"()" for multiple blocks of data
+ * 
+ * @param memory_address address to start the 16 bits writing to
+ * @param read_memory_data data pointer for the write data
+ * @return int 
+ */
+static int universal_write_16bit(universal_usb_handle* multilink,
+		unsigned long memory_address, unsigned long memory_data)
+{
+	unsigned long memory_access_tag = 0;
+	mem_result write_result;
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	device_api->write_16bit_value(memory_access_tag, memory_address, memory_data, 
+			(void*) &write_result);
+
+	if (write_result != MEM_OK) {
+		LOG_ERROR("universal_write_16bit() failed: error='%d'", write_result);
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Reads 8 bits of data.
+ * 
+ * Reads 8 bits of data from a specified memory address location. 
+ * To read more data than 8 bits use the following:
+ * - <universal_read_16bit>"()" for 16 bits of data
+ * - <universal_read_32bit>"()" for 32 bits of data
+ * - <universal_read_block>"()" for multiple blocks of data
+ * 
+ * @param memory_address address to start the 8 bits read from
+ * @param read_memory_data data pointer for the read data
+ * @return unsigned char 
+ */
+unsigned char universal_read_8bit(universal_usb_handle* multilink,
+		unsigned long memory_address, unsigned short* read_memory_data)
+{
+	unsigned long memory_access_tag = 0;
+	mem_result read_result;
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	unsigned char read_value = device_api->read_8bit_value(memory_access_tag, 
+			memory_address, &read_result);
+
+	if (read_result != MEM_OK) {
+		LOG_ERROR("universal_read_8bit() failed: error='%d'", read_result);
+		return ERROR_FAIL;
+	}
+
+	*read_memory_data = read_value;
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Write 8 bits of data.
+ *
+ * Writes 8 bits of data to a specified memory address location. 
+ * 
+ * To write more data than 8 bits use the following:
+ * - <universal_write_16bit>"()" for 16 bits of data
+ * - <universal_write_32bit>"()" for 32 bits of data
+ * - <universal_write_block>"()" for multiple blocks of data
+ *  
+ * @param memory_address address to start the 8 bits writing to
+ * @param read_memory_data data pointer for the write data
+ * @return int 
+ */
+static int universal_write_8bit(universal_usb_handle* multilink,
+		unsigned long memory_address, unsigned long memory_data)
+{
+	unsigned long memory_access_tag = 0;
+	mem_result write_result;
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	device_api->write_8bit_value(memory_access_tag, memory_address, memory_data, 
+			&write_result);
+
+	if (write_result != MEM_OK) {
+		LOG_ERROR("universal_write_8bit() failed: error='%d'", write_result);
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Reads block of data
+ * 
+ * This routine allows a faster read rate of the target memory than reading 
+ * individual memory locations,especially across the USB or Ethernet medium.
+ * Data is read starting at the "address" and placed in the PC buffer.
+ * 
+ * @param memory_address The address of memory block to read
+ * @param num_bytes The size of memory block to read
+ * @param access_bit_size The data memory block to store read data
+ * @param buffer_ptr Determines the number of bits read at a time
+ * @return int 
+ */
+static int universal_read_block(universal_usb_handle* multilink,
+		unsigned long memory_address, unsigned long num_bytes,
+		unsigned long access_bit_size, unsigned char *buffer_ptr)
+{
+	unsigned long memory_access_tag = 0;
+	unsigned char read_result;
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	if ((num_bytes == 0) || (buffer_ptr == NULL)){
+		LOG_ERROR("universal_read_block() failed: invalid data values");
+		return ERROR_FAIL;
+	}
+	if ((access_bit_size != MEM_8BIT) || (access_bit_size != MEM_16BIT) || 
+			(access_bit_size != MEM_32BIT)) {
+		LOG_ERROR("universal_read_block() failed: invalid access bit size");
+		return ERROR_FAIL;
+	}
+
+	bool read_status = device_api->get_block(memory_access_tag, memory_address,
+			num_bytes, access_bit_size, buffer_ptr, &read_result);
+
+	// Check if the DLL command was executed correctly
+	if (read_status == false) {
+		LOG_ERROR("universal_read_block() failed: dll read status");
+		return ERROR_FAIL;
+	}
+
+	// FIXME: Check bit logic
+	// Check the read status of the number of bits read per transfer
+	for (unsigned long x = 0; x < access_bit_size; x++) {
+		// Select required bit and filter other bits
+		unsigned long bit_value = ((read_result >> x) && 0x01);
+		if (bit_value != MEM_OK) {
+			LOG_ERROR("universal_read_block() failed: memory read result");
+			return ERROR_FAIL;
+		}
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Writes block of data.
+ * 
+ * This routine allows a faster write rate of the target memory than writing 
+ * individual memory locations,especially across the USB or Ethernet medium. 
+ * Data, placed in the PC buffer, is write starting at the "address"
+ * 
+ * @param memory_address The address of memory block to write
+ * @param num_bytes The size of memory block to write
+ * @param access_bit_size The data memory block with prepared data to write
+ * @param buffer_ptr Determines the number of bits writes at a time
+ * @return int 
+ */
+static int universal_write_block(universal_usb_handle* multilink,
+		unsigned long memory_address, unsigned long num_bytes,
+		unsigned long access_bit_size, unsigned char *buffer_ptr)
+{
+	unsigned long memory_access_tag = 0;
+	unsigned char write_result;
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	if ((num_bytes == 0) || (buffer_ptr == NULL)){
+		LOG_ERROR("universal_write_block() failed: invalid data values");
+		return ERROR_FAIL;
+	}
+	if ((access_bit_size != MEM_8BIT) || (access_bit_size != MEM_16BIT) || 
+			(access_bit_size != MEM_32BIT)) {
+		LOG_ERROR("universal_write_block() failed: invalid access bit size");
+		return ERROR_FAIL;
+	}
+
+	bool write_status = device_api->put_block(memory_access_tag, memory_address, 
+			num_bytes, access_bit_size, buffer_ptr, &write_result);
+
+	// Check if the DLL command was executed correctly
+	if (write_status == false) {
+		LOG_ERROR("universal_write_block() failed: dll write status");
+		return ERROR_FAIL;
+	}
+
+	// FIXME: Check bit logic
+	// Check the read status of the number of bits read per transfer
+	for (unsigned long x = 0; x < access_bit_size; x++) {
+		// Select required bit and filter other bits
+		unsigned long bit_value = ((write_result >> x) && 0x01);
+		if (bit_value != MEM_OK) {
+			LOG_ERROR("universal_write_block() failed: memory write result");
+			return ERROR_FAIL;
+		}
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Load the binary file into target memory.
+ * 
+ * @param file_name 
+ * @param start_address 
+ * @return int 
+ */
+static int universal_load_bin_file(universal_usb_handle* multilink,
+		char *file_name, unsigned long start_address)
+{
+	struct ml_device_api_s* device_api = multilink->usb_device;
+	
+	if (strcmp(file_name, "")) {
+		LOG_ERROR("universal_load_bin_file() failed: Invalid file name");
+		return ERROR_FAIL;
+	}
+
+	bool load_state = device_api->load_bin_file(file_name, start_address);
+	if (load_state == false) {
+		LOG_ERROR("universal_load_bin_file() falied: Cant load file");
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief Load the S-record file into target memory.
+ * 
+ * @param file_name 
+ * @param start_address 
+ * @return int 
+ */
+static int universal_load_srec_file(universal_usb_handle* multilink,
+		char *file_name, unsigned long start_address)
+{
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	if (strcmp(file_name, "")) {
+		LOG_ERROR("universal_load_srec_file() failed: Invalid file name");
+		return ERROR_FAIL;
+	}
+
+	bool load_state = device_api->load_srec_file(file_name, start_address);
+	if (load_state == false) {
+		LOG_ERROR("universal_load_srec_file() falied: Cant load file");
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief 
+ * 
+ * @param register_number 
+ * @param register_value 
+ * @return int 
+ */
+static int universal_get_mcu_registers(universal_usb_handle* multilink,
+		unsigned long register_number, unsigned long *register_value)
+{
+	unsigned long register_access_tags = 0;
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	bool get_status = device_api->get_mcu_register(register_access_tags, 
+			register_number, register_value);
+	if (get_status == false) {
+		LOG_ERROR("universal_get_mcu_registers(): "
+				"Could not get register '%lu'", register_number);
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief 
+ * 
+ * @param register_number 
+ * @param register_value 
+ * @return int 
+ */
+static int universal_set_mcu_registers(universal_usb_handle* multilink,
+		unsigned long register_number, unsigned long register_value) 
+{
+	unsigned long register_access_tags = 0;
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	bool get_status = device_api->set_mcu_register(register_access_tags, 
+			register_number, register_value);
+	if (get_status == false) {
+		LOG_ERROR("universal_set_mcu_registers(): Could not set register '%lu'", 
+				register_number);
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief 
+ * 
+ * @param swd_last_status 
+ * @return int 
+ */
+static int universal_swd_status(universal_usb_handle* multilink,
+		unsigned long* swd_last_status)
+{
+	struct ml_device_api_s* device_api = multilink->usb_device;
+	bool swd_state = device_api->command_and_control(ARM_GET_LAST_SWD_STATUS, true, 
+			0, 0, 0, &swd_last_status, NULL);
+	if (swd_state == false) {
+		LOG_ERROR("universal_swd_status(): Could get SWD status");
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief 
+ * 
+ * @return int 
+ */
+static int universal_check_swd_error(universal_usb_handle* multilink)
+{ 
+	unsigned long swd_last_status;
+	struct ml_device_api_s* device_api = multilink->usb_device;
+
+	int state = universal_swd_status(&swd_last_status);
+	if (state != ERROR_OK) {
+		return state;
+	}
+
+	if (swd_last_status != SWD_STATUS_ACK) {
+		//
+		if (swd_last_status != SWD_STATUS_FAULT) {
+			// Varify commuications with TAP JTAG interface
+			unsigned char probe_error = device_api->check_critical_error();
+			if (probe_error & 0x08) {
+				universal_reset_interface();
+			}
+
+			// Reset MCU
+			universal_reset_target();
+		}
+		//
+		LOG_ERROR("universal_check_swd_error() failed: status='%lu'", 
+				swd_last_status);
+
+		return ERROR_FAIL;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief 
+ * 
+ * @param register_address 
+ * @param immediate_read 
+ * @param return_value 
+ * @return int 
+ */
+static int universal_read_dp_register(universal_usb_handle* multilink,
+		unsigned long register_address, bool immediate_read,
+		unsigned long* return_value)
+{
+	struct ml_device_api_s* device_api = multilink->usb_device;
+	bool read_state = device_api->command_and_control(ARM_READ_DP_REGISTER,
+			immediate_read, register_address, 0, 0, &return_value, NULL);
+	if (read_state == false) {
+		LOG_ERROR("universal_read_dp_register() failed: address='%lu',"
+				" return value='%lu'", register_address, *return_value);
+		return ERROR_FAIL;
+	}
+
+	bool swd_state = universal_check_swd_error();
+	if (swd_state != ERROR_OK) {
+		return swd_state;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief 
+ * 
+ * @param register_address 
+ * @param register_value 
+ * @param immediate_write 
+ * @return int 
+ */
+static int universal_write_dp_register(universal_usb_handle* multilink,
+		unsigned long register_address, unsigned long register_value,
+		bool immediate_write)
+{
+	struct ml_device_api_s* device_api = multilink->usb_device;
+	bool write_state = device_api->command_and_control(ARM_WRITE_DP_REGISTER, 
+			immediate_write, register_address, register_value, 0, NULL, NULL);
+
+	if (write_state == false) {
+		LOG_ERROR("universal_write_dp_register() failed: address='%lu', "
+				"value='%lu'", register_address, register_value);
+		return ERROR_FAIL;
+	}
+
+	bool swd_state = universal_check_swd_error();
+	if (swd_state != ERROR_OK) {
+		return swd_state;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief 
+ * 
+ * @param access_port 
+ * @param register_address 
+ * @param immediate_read 
+ * @param return_value 
+ * @return int 
+ */
+static int universal_read_ap_register(universal_usb_handle* multilink,
+		unsigned long access_port, unsigned long register_address,
+		bool immediate_read, unsigned long* return_value)
+{
+	struct ml_device_api_s* device_api = multilink->usb_device;
+	bool read_state = device_api->command_and_control(ARM_READ_AP_REGISTER,
+			immediate_read, access_port, register_address, 0, &return_value,
+			NULL);
+	if (read_state == false) {
+		LOG_ERROR("universal_read_dp_register() failed: address='%lu',"
+				" return value='%lu'", register_address, *return_value);
+	}
+
+	bool swd_state = universal_check_swd_error();
+	if (swd_state != ERROR_OK) {
+		return swd_state;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+#if UNUSED_FUNCTION == 1
+/**
+ * @brief 
+ * 
+ * @param access_port 
+ * @param register_address 
+ * @param register_value 
+ * @param immediate_write 
+ * @return int 
+ */
+static int universal_write_ap_register(universal_usb_handle* multilink,
+		unsigned long access_port, unsigned long register_address, 
+		unsigned long register_value, bool immediate_write)
+{
+	struct ml_device_api_s* device_api = multilink->usb_device;
+	bool write_state = device_api->command_and_control(ARM_WRITE_AP_REGISTER,
+			immediate_write, access_port, register_address, register_value,
+			NULL, NULL);
+	if (write_state == false) {
+		LOG_ERROR("universal_write_ap_register() failed: address='%lu', "
+				"value='%lu'", register_address, register_value);
+		return ERROR_FAIL;
+	}
+
+	bool swd_state = universal_check_swd_error();
+	if (swd_state != ERROR_OK) {
+		return swd_state;
+	}
+
+	return ERROR_OK;
+}
+#endif
+
+
+/******************************************************************************
+ * OpenOCD Function Calls -
+ ******************************************************************************/
+
+/**
+ * @brief Close device_state is there are no more connections
+ * 
+ * @param handle 
+ * @return int 
+ */
+static int universal_usb_close(void *handle)
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+	struct ml_device_state_s* device_state = multilink->device_state;
+
+	// Check for mutiple open connections
+	if (device_state->ref_count == 0) {
+		return ERROR_OK;
+	} else {
+		device_state->ref_count -= 1;
+	}
+
+	// If all connection have been disconnected then flush data and close
+	if (device_state->ref_count == 0) {
+		// Turn off device_state
+		bool flush_state = universal_flush(multilink);
+		if (flush_state != ERROR_OK) {
+			return flush_state;
+		}
+		
+		// Turn off device_state
+		bool pwr_state = universal_power_off(multilink);
+		if (pwr_state != ERROR_OK) {
+			return pwr_state;
+		}
+
+		// Cleanly unload DLL
+		int dll_state = ml_universal_unload_dll(device_state);
+		if (dll_state != 0) {
+			LOG_ERROR("universal_quit() failed: %s", dlerror());
+			return ERROR_COMMAND_CLOSE_CONNECTION;
+		}
+	}
+
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param handle 
+ * @param idcode 
+ * @return int 
+ */
+static int universal_usb_idcode(void *handle, uint32_t *idcode) 
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+	(void) multilink;
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param handle 
+ * @return enum target_state 
+ */
+static enum target_state universal_usb_state(void *handle) 
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+	(void) multilink;
+	return TARGET_RUNNING;
+}
+
+/**
+ * @brief 
+ * 
+ * @param handle 
+ * @return int 
+ */
+static int universal_usb_reset(void *handle) 
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+
+	//LOG_DEBUG_IO("function called: TRST=%d, SRST=%d", trst, srst);
+	universal_flush(multilink);
+	universal_reset_interface(multilink);
+	universal_flush(multilink);
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param handle 
+ * @param srst 
+ * @return int 
+ */
+static int universal_usb_assert_srst(void *handle, int srst)
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+	(void) multilink;
+	(void) srst;
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param handle 
+ * @return int 
+ */
+static int universal_usb_run(void *handle) 
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+	(void) multilink;
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param handle 
+ * @return int 
+ */
+static int universal_usb_halt(void *handle) 
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+
+	if(universal_target_running(multilink) == 1) {
+		int halt_state = universal_halt_target(multilink);
+		if (halt_state) {
+			return ERROR_OK;
+		} else {
+			return ERROR_FAIL;
+		}
+	} else {
+		LOG_DEBUG("universal_usb_halt(): Target already halted");
+	}
+
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param handle 
+ * @return int 
+ */
+static int universal_usb_step(void *handle) 
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+	(void) multilink;
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param handle 
+ * @param regsel 
+ * @param val 
+ * @return int 
+ */
+static int universal_usb_read_reg(void *handle, unsigned int regsel,
+		uint32_t *val)
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+	(void) multilink;
+	(void) regsel;
+	(void) val;
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param handle 
+ * @param regsel 
+ * @param val 
+ * @return int 
+ */
+static int universal_usb_write_reg(void *handle, unsigned int regsel,
+		uint32_t val)
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+	(void) multilink;
+	(void) regsel;
+	(void) val;
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param handle 
+ * @param addr 
+ * @param size 
+ * @param count 
+ * @param buffer 
+ * @return int 
+ */
+static int universal_usb_read_mem(void *handle, uint32_t addr, uint32_t size,
+		uint32_t count, uint8_t *buffer)
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+	(void) multilink;
+	(void) addr;
+	(void) size;
+	(void) count;
+	(void) buffer;
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param handle 
+ * @param addr 
+ * @param size 
+ * @param count 
+ * @param buffer 
+ * @return int 
+ */
+static int universal_usb_write_mem(void *handle, uint32_t addr, uint32_t size,
+		uint32_t count, const uint8_t *buffer)
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+	(void) multilink;
+	(void) addr;
+	(void) size;
+	(void) count;
+	(void) buffer;
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param handle 
+ * @param addr 
+ * @param val 
+ * @return int 
+ */
+static int universal_usb_write_debug_reg(void *handle, uint32_t addr,
+		uint32_t val)
+{
+	struct ml_handle_s* multilink = (struct ml_handle_s*) handle;
+	(void) multilink;
+	(void) addr;
+	(void) val;
+	return ERROR_OK;
+}
+
+/**
+ * @brief 
+ * 
+ * @param param 
+ * @param fd 
+ * @return int 
+ */
+static int universal_usb_open(struct hl_interface_param_s *param, void **fd)
+{
+	LOG_DEBUG_IO("function called");
+
+	struct ml_handle_s *device_handle;
+	struct ml_device_state_s* device_state;
+	struct ml_device_api_s* device_api;
+
+	///
+	device_handle = calloc(1, sizeof(struct ml_handle_s));
+	if (device_handle == 0) {
+		LOG_ERROR("universal_usb_open() failed: unable to allocate memory"
+				"for device handle");
+		return ERROR_FAIL;
+	} else {
+		device_handle->device_state = NULL;
+		device_handle->device_api = NULL;
+	}
+
+	//
+	device_state = calloc(1, sizeof(struct ml_device_state_s));
+	if (device_state == 0) {
+		LOG_ERROR("unable to allocate memory for device state");
+		return ERROR_FAIL;
+	}
+	device_handle->device_state = device_state;
+	//
+	device_api = calloc(1, sizeof(struct ml_device_api_s));
+	if (device_api == 0) {
+		LOG_ERROR("unable to allocate memory for device api");
+		return ERROR_FAIL;
+	}
+	device_handle->device_api = device_api;
+	//
+
+	/// Check the USB information
+	if (!param->vid[0] && !param->pid[0]) {
+		LOG_ERROR("Missing vid/pid");
+		return ERROR_FAIL;
+	}
+	//
+	for (uint8_t i = 0; param->vid[i] && param->pid[i]; ++i) {
+		LOG_DEBUG("transport: %d vid: 0x%04x pid: 0x%04x serial: %s", 
+				param->transport,param->vid[i], param->pid[i], 
+				param->serial ? param->serial : "");
+	}
+
+	/// Load the DLL and assign function pointers
+	dlerror();		//< Purge any outsanding errors
+	int dll_state = ml_universal_load_dll(device_state, device_api,
+			NULL);
+	if (dll_state != 1) {
+		LOG_ERROR("ml_universal_load_dll() failed: %s", dlerror());
+		return ERROR_JTAG_INIT_FAILED;
+	}
+
+	/// Display relevent data about P&E device_state interface
+	universal_display_cable_version(device_handle);
+	universal_list_devices(device_handle, NULL);
+	universal_display_version(device_handle);
+	universal_display_devices(device_handle, false);
+
+	/// Device initialisation
+	char port_name[1000] = "";
+	// Set port to devices default name if not set
+	if (!strcmp(port_name, "")) {
+		strcpy(port_name, "USB1\0");
+	}
+	//
+	bool open_state = universal_open_port(device_handle, port_name);
+	if (open_state != ERROR_OK) {
+		return open_state;
+	}
+
+	// System has been setup correctly so store system state
+	device_state->ref_count += 1;
+
+	bool reset_state = universal_reset_interface(device_handle);
+	if (reset_state != ERROR_OK) {
+		return reset_state;
+	}
+
+	/// Device configuration
+	LOG_DEBUG_IO("function called");
+
+	bool comms_state;
+
+	if (param->transport == HL_TRANSPORT_JTAG) {
+		LOG_DEBUG_IO("Multilink Universal connecting to JTAG");
+		comms_state = universal_enable_jtag(device_handle);
+	} else {
+		LOG_DEBUG_IO("Multilink Universal connecting to SWD");
+		comms_state = universal_enable_swd(device_handle);
+	}
+
+	if (comms_state != ERROR_OK) {
+		return comms_state;
+	}
+
+	// Set frequency
+	universal_usb_debug_speed(device_handle, 1000000, false);
+	universal_power_on(device_handle);
+
+	bool debug_state = universal_enable_debug(device_handle);
+	if (debug_state != ERROR_OK) {
+		return debug_state;
+	}
+	
+	*fd = device_handle;
+
+	return ERROR_OK;
+}
+
+/******************************************************************************
+ * OpenOCD - Driver registration and setup
+ ******************************************************************************/
+
+/**
+ * @brief 
+ * 
+ */
+struct hl_layout_api_s multilink_usb_layout_api = {
+	.open = universal_usb_open,
+	.close = universal_usb_close,
+	.reset = universal_usb_reset,
+	.assert_srst = universal_usb_assert_srst,
+	.run = universal_usb_run,
+	.halt = universal_usb_halt,
+	.step = universal_usb_step,
+	//.read_regs=,
+	.read_reg = universal_usb_read_reg,
+	.write_reg = universal_usb_write_reg,
+	.read_mem = universal_usb_read_mem,
+	.write_mem = universal_usb_write_mem,
+	.write_debug_reg = universal_usb_write_debug_reg,
+	.idcode = universal_usb_idcode,
+	//.custom_command=,
+	.speed = universal_usb_debug_speed,
+	//.config_trace=,
+	//.poll_trace=,
+	.state = universal_usb_state
+};
diff --git a/src/jtag/drivers/multilink.h b/src/jtag/drivers/multilink.h
new file mode 100644
index 000000000..2b3587eb2
--- /dev/null
+++ b/src/jtag/drivers/multilink.h
@@ -0,0 +1,898 @@
+/**
+ * @file multilink.h
+ * @author Christopher West (cw...@thedigitaledge.co.uk)
+ * @brief Interface to the P&E Micro Multilink Universal devices to OpenOCD
+ * @version 0.1
+ * @date 2021-09-20
+ * 
+ * @copyright Copyright (c) 2021, BSD-3-Clause
+ * 
+ * Information taken form:
+ *   https://github.com/NXPmicro/pypemicro/blob/master/pypemicro/pemicro.py
+ */
+
+#ifndef OPENOCD_JTAG_DRIVERS_MULTILINK_UNIVERSAL_H
+#define OPENOCD_JTAG_DRIVERS_MULTILINK_UNIVERSAL_H
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <assert.h>
+
+/****************************************************************************
+ *																			*
+ ****************************************************************************/
+
+#define UNUSED_FUNCTION 0
+#define UNIVERSAL_HID_MAX_SIZE	(1024)
+
+/**
+ * @brief Supported PEMicro port types
+ * 
+ */
+typedef enum _ml_port_types {
+	PARALLEL_PORT_CABLE = 1,
+	PCIBDM_LIGHTNING = 2,
+	USB_MULTILINK = 3,
+	CYCLONE_PRO_MAX_SERIAL = 4,
+	CYCLONE_PRO_MAX_USB = 5,
+	CYCLONE_PRO_MAX_ETHERNET = 6,
+	OPENSDA_USB = 9,
+	AUTODETECT = 99
+} ml_port_types;
+
+
+/**
+ * @brief PEMicro special features
+ * 
+ */
+typedef enum _ml_mem_result {
+	MEM_OK = 0,
+	MEM_NO_ACCESS = 1,
+	MEM_BUS_ERROR = 2,
+	MEM_UNIMPLEMENTED = 3,
+	MEM_UNINITIALIZED = 4,
+	MEM_PROGRAMMING_ERROR = 5
+} ml_mem_result;
+
+/**
+ * @brief Interfaces for the PEMicro
+ * 
+ */
+typedef enum _ml_interface_type {
+	JTAG_COMMS = 0,
+	SWD_COMMS = 1
+} ml_interface_type;
+
+/**
+ * @brief SWD status values
+ * 
+ */
+typedef enum _ml_swd_status {
+	SWD_STATUS_FAULT = 0X01,
+	SWD_STATUS_WAIT = 0X02,
+	SWD_STATUS_ACK = 0X04
+} ml_swd_status;
+
+/**
+ * @brief PEMicro Special features.
+ * 
+ */
+enum ml_pe_micro_features {
+	/// Special Features for Power Management
+	PWR_SET_POWER_OPTIONS = 0x8000001,
+	PWR_TURN_POWER_ON = 0x38000011,
+	PWR_TURN_POWER_OFF = 0x38000012,
+	/// Special Features for debug communications mode
+	ARM_SET_DEBUG_COMM_SWD = 0x00000000,
+	ARM_SET_DEBUG_COMM_JTAG = 0x00000001,
+	///
+	ARM_SET_COMMUNICATIONS_MODE = 0x44000001,
+	ARM_ENABLE_DEBUG_MODULE = 0x44000002,
+	ARM_WRITE_AP_REGISTER = 0x44000003,
+	ARM_READ_AP_REGISTER = 0x44000004,
+	ARM_FLUSH_ANY_QUEUED_DATA = 0x44000005,
+	ARM_WRITE_DP_REGISTER = 0x44000007,
+	ARM_READ_DP_REGISTER = 0x44000008,
+	/// SWD control special features
+	ARM_GET_LAST_SWD_STATUS = 0x44000006,
+	/// Special Features for setting current device and core
+	GENERIC_GET_DEVICE_LIST = 0x58004000,
+	GENERIC_SELECT_DEVICE = 0x58004001,
+	GENERIC_GET_CORE_LIST = 0x58004002,
+	GENERIC_SELECT_CORE = 0x58004003,
+	SET_DEFAULT_APP_FILES_DIR = 0x58006000
+};
+
+/**
+ * @brief Access size of block memory operations
+ * 
+ *
+typedef enum ml_memory_access_size {
+	MEM_8BIT = 1,
+	MEM_16BIT = 2,
+	MEM_32BIT = 4
+};
+*/
+
+/**
+ * @brief Arm registers used for Writing/Readin operations
+ * 
+ */
+enum ml_arm_registers {
+	/// Core registers
+	ARM_REG_R0 = 0,
+	ARM_REG_R1 = 1,
+	ARM_REG_R2 = 2,
+	ARM_REG_R3 = 3,
+	ARM_REG_R4 = 4,
+	ARM_REG_R5 = 5,
+	ARM_REG_R6 = 6,
+	ARM_REG_R7 = 7,
+	ARM_REG_R8 = 8,
+	ARM_REG_R9 = 9,
+	ARM_REG_R10 = 10,
+	ARM_REG_R11 = 11,
+	ARM_REG_R12 = 12,
+	ARM_REG_R13 = 13,
+	ARM_REG_R14 = 14,
+	ARM_REG_R15 = 15,
+	ARM_REG_SP = ARM_REG_R13,
+	ARM_REG_LR = ARM_REG_R14,
+	ARM_REG_PC = ARM_REG_R15,
+	/// Program status registers + Stack pointers
+	ARM_REG_XPSR = 16,
+	ARM_REG_MSP = 17,	 // Main SP
+	ARM_REG_PSP = 18,	 // Process SP
+	// Special registers
+	// CONTROL bits [31:24]
+	// FAULTMASK bits [23:16]
+	// BASEPRI bits [15:8]
+	// PRIMASK bits [7:0]
+	ARM_REG_SPECIAL_REG = 20,
+	/// Floating-Point Status and Control Register
+	ARM_REG_FPSCR = 33,
+	/// Floating point registers
+	ARM_REG_S0 = 64,
+	ARM_REG_S1 = 65,
+	ARM_REG_S2 = 66,
+	ARM_REG_S3 = 67,
+	ARM_REG_S4 = 68,
+	ARM_REG_S5 = 69,
+	ARM_REG_S6 = 70,
+	ARM_REG_S7 = 71,
+	ARM_REG_S8 = 72,
+	ARM_REG_S9 = 73,
+	ARM_REG_S10 = 74,
+	ARM_REG_S11 = 75,
+	ARM_REG_S12 = 76,
+	ARM_REG_S13 = 77,
+	ARM_REG_S14 = 78,
+	ARM_REG_S15 = 79,
+	ARM_REG_S16 = 80,
+	ARM_REG_S17 = 81,
+	ARM_REG_S18 = 82,
+	ARM_REG_S19 = 83,
+	ARM_REG_S20 = 84,
+	ARM_REG_S21 = 85,
+	ARM_REG_S22 = 86,
+	ARM_REG_S23 = 87,
+	ARM_REG_S24 = 88,
+	ARM_REG_S25 = 89,
+	ARM_REG_S26 = 90,
+	ARM_REG_S27 = 91,
+	ARM_REG_S28 = 92,
+	ARM_REG_S29 = 93,
+	ARM_REG_S30 = 94,
+	ARM_REG_S31 = 95,
+	/// MDM-AP Status Register
+	ARM_REG_MDM_AP = 1000
+};
+
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+
+#define MULTILINK_STATIC_LIBRARY 0
+
+#if MULTILINK_STATIC_LIBRARY == 1
+/**
+ * @brief Sends command & control messages to the multilink universal device
+ * 
+ * @param featurenum Feature index/number
+ * @param set_feature Set feature
+ * @param param_value_1 First parameter
+ * @param param_value_2 Second parameter
+ * @param param_value_3 Third parameter
+ * @param param_reference_1 Reference to first return value
+ * @param param_reference_2 Reference to second return value
+ * @return true if command succeeded
+ * @return false if command failed
+ */
+bool pe_special_features(unsigned long featurenum, bool set_feature, 
+		unsigned long param_value_1, unsigned long param_value_2,
+		unsigned long param_value_3, void *param_reference_1,
+		void *param_reference_2);
+
+/**
+ * @brief 
+ * 
+ * @param port_type 
+ * @param port_num 
+ * @return true 
+ * @return false 
+ */
+bool open_port(unsigned long port_type, unsigned long port_num);
+
+/**
+ * @brief 
+ * 
+ */
+void close_port(void);
+
+/**
+ * @brief 
+ * 
+ * @param port_identifier 
+ * @return true 
+ * @return false 
+ */
+bool open_port_by_identifier(char* port_identifier);
+
+/**
+ * @brief 
+ * 
+ * @return true 
+ * @return false 
+ */
+bool reenumerate_all_port_types(void);
+
+/**
+ * @brief Get the enumerated number of ports object
+ * 
+ * @param port_type 
+ * @return unsigned long 
+ */
+unsigned long get_enumerated_number_of_ports(unsigned long port_type);
+
+/**
+ * @brief Get the port descriptor object
+ * 
+ * @param port_type 
+ * @param port_num 
+ * @return char* 
+ */
+char* get_port_descriptor(unsigned long port_type, unsigned long port_num);
+
+/**
+ * @brief Get the port descriptor short object
+ * 
+ * @param port_type 
+ * @param port_num 
+ * @return char* 
+ */
+char* get_port_descriptor_short(unsigned long port_type, unsigned long port_num);
+
+/**
+ * @brief 
+ * 
+ */
+void reset_hardware_interface(void);
+
+/**
+ * @brief 
+ * 
+ * @return unsigned char 
+ */
+unsigned char check_critical_error(void);
+
+/**
+ * @brief 
+ * 
+ * @return char* 
+ */
+char* version(void);
+
+/**
+ * @brief Get the dll version object
+ * 
+ * @return unsigned short 
+ */
+unsigned short get_dll_version(void);
+
+/**
+ * @brief Set the debug shift frequency object
+ * 
+ * @param shift_speed_in_hz 
+ */
+void set_debug_shift_frequency(signed long shift_speed_in_hz);
+
+/**
+ * @brief Set the reset delay in ms object
+ * 
+ * @param delaylength 
+ */
+void set_reset_delay_in_ms(unsigned long delay_length);
+
+/**
+ * @brief 
+ * 
+ * @return true 
+ * @return false 
+ */
+bool target_reset(void);
+
+/**
+ * @brief 
+ * 
+ * @return true 
+ * @return false 
+ */
+bool target_check_if_halted(void);
+
+/**
+ * @brief 
+ * 
+ * @return true 
+ * @return false 
+ */
+bool target_halt(void);
+
+/**
+ * @brief 
+ * 
+ * @return true 
+ * @return false 
+ */
+bool target_resume(void);
+
+/**
+ * @brief 
+ * 
+ * @return true 
+ * @return false 
+ */
+bool target_step(void);
+
+/**
+ * @brief Set the reset pin state object
+ * 
+ * @param state 
+ */
+void set_reset_pin_state(unsigned char state);
+
+/**
+ * @brief 
+ * 
+ * @param file_name 
+ */
+void open_debug_file(char *file_name);
+
+/**
+ * @brief 
+ * 
+ * @param file_name 
+ */
+void close_debug_file(char *file_name);
+
+/**
+ * @brief 
+ * 
+ * @param memory_access_tag 
+ * @param memory_address 
+ * @param read_result 
+ * @return unsigned long 
+ */
+unsigned long read_32bit_value(unsigned long memory_access_tag, 
+		unsigned long memory_address, mem_result *read_result);
+/**
+ * @brief 
+ * 
+ * @param memory_access_tag 
+ * @param memory_address 
+ * @param memory_data 
+ * @param write_result 
+ */
+void write_32bit_value(unsigned long memory_access_tag, unsigned long memory_address,
+		unsigned long memory_data, mem_result *write_result);
+
+/**
+ * @brief 
+ * 
+ * @param memory_access_tag 
+ * @param memory_address 
+ * @param read_result 
+ * @return unsigned short 
+ */
+unsigned short read_16bit_value(unsigned long memory_access_tag,
+		unsigned long memory_address, mem_result *read_result);
+
+/**
+ * @brief 
+ * 
+ * @param memory_access_tag 
+ * @param memory_address 
+ * @param memory_data 
+ * @param write_result 
+ */
+void write_16bit_value(unsigned long memory_access_tag, unsigned long memory_address,
+		unsigned long memory_data, mem_result *write_result);
+
+/**
+ * @brief 
+ * 
+ * @param memory_access_tag 
+ * @param memory_address 
+ * @param read_result 
+ * @return unsigned char 
+ */
+unsigned char read_8bit_value(unsigned long memory_access_tag,
+		unsigned long memory_address, mem_result *read_result);
+
+/**
+ * @brief 
+ * 
+ * @param memory_access_tag 
+ * @param memory_address 
+ * @param memory_data 
+ * @param write_result 
+ */
+void write_8bit_value(unsigned long memory_access_tag, unsigned long memory_address,
+		unsigned long memory_data, mem_result *write_result);
+
+/**
+ * @brief Get the block object
+ * 
+ * @param memory_access_tag 
+ * @param memory_address 
+ * @param num_bytes 
+ * @param access_sizing 
+ * @param buffer_ptr 
+ * @param read_result 
+ * @return true 
+ * @return false 
+ */
+bool get_block(unsigned long memory_access_tag, unsigned long memory_address,
+		unsigned long num_bytes, unsigned long access_sizing, 
+		unsigned char *buffer_ptr, unsigned char *read_result);
+
+/**
+ * @brief 
+ * 
+ * @param memory_access_tag 
+ * @param memory_address 
+ * @param num_bytes 
+ * @param access_sizing 
+ * @param buffer_ptr 
+ * @param write_result 
+ * @return true 
+ * @return false 
+ */
+bool put_block(unsigned long memory_access_tag, unsigned long memory_address,
+		unsigned long num_bytes, unsigned long access_sizing, 
+		unsigned char *buffer_ptr, unsigned char *write_result);
+
+/**
+ * @brief 
+ * 
+ * @param file_name 
+ * @param memory_start_address 
+ * @return true 
+ * @return false 
+ */
+bool load_bin_file(char *file_name, unsigned long memory_start_address);
+
+/**
+ * @brief 
+ * 
+ * @param file_name 
+ * @param memory_start_address 
+ * @return true 
+ * @return false 
+ */
+bool load_srec_file(char *file_name, unsigned long memory_start_address);
+
+/**
+ * @brief Get the mcu register object
+ * 
+ * @param register_access_tags 
+ * @param reg_num 
+ * @param reg_value 
+ * @return true 
+ * @return false 
+ */
+bool get_mcu_register(unsigned long register_access_tags, unsigned long reg_num,
+		unsigned long *reg_value);
+
+/**
+ * @brief Set the mcu register object
+ * 
+ * @param register_access_tags 
+ * @param reg_num 
+ * @param reg_value 
+ * @return true 
+ * @return false 
+ */
+bool set_mcu_register(unsigned long register_access_tags, unsigned long reg_num,
+		unsigned long reg_value);
+#else
+
+/******************************************************************************
+ * 
+ ******************************************************************************/
+
+
+/**
+ * @brief P&E Multilink - Dynamicly linked library interface
+ * 
+ */
+struct ml_device_api_s {
+
+	/**
+	 * @brief Sends command & control messages to the multilink device
+	 * 
+	 * @param featurenum Feature index/number
+	 * @param set_feature Set feature
+	 * @param param_value_1 First parameter
+	 * @param param_value_2 Second parameter
+	 * @param param_value_3 Third parameter
+	 * @param param_reference_1 Reference to first return value
+	 * @param param_reference_2 Reference to second return value
+	 * @return true if command succeeded
+	 * @return false if command failed
+	 */
+	bool (*command_and_control)(unsigned long featurenum, bool set_feature, 
+			unsigned long param_value_1, unsigned long param_value_2, 
+			unsigned long param_value_3, void* param_reference_1, 
+			void* param_reference_2);
+
+	/**
+	 * @brief Open connection to the multilink device
+	 * 
+	 * @param port_type 
+	 * @param port_num 
+	 * @return true 
+	 * @return false 
+	 */
+	bool (*open_port)(unsigned long port_type, unsigned long port_num);
+
+	/**
+	 * @brief Open connection to the multilink device by character identifier
+	 * 
+	 * @param port_identifier idenfitfer string
+	 * @return true connection was opened
+	 * @return false connection couldn't be opened
+	 */
+	bool (*open_port_by_identifier)(char* port_identifier);
+
+	/**
+	 * @brief Close connection to the multilink device
+	 * 
+	 */
+	void (*close_port)(void);
+
+	/**
+	 * @brief 
+	 * 
+	 * @return true 
+	 * @return false 
+	 */
+	bool (*reenumerate_all_port_types)(void);
+
+	/**
+	 * @brief Get the enumerated number of ports object
+	 * 
+	 * @param port_type 
+	 * @return unsigned long 
+	 */
+	unsigned long (*get_enumerated_number_of_ports)(unsigned long port_type);
+
+	/**
+	 * @brief Get the port descriptor object
+	 * 
+	 * @param port_type 
+	 * @param port_num 
+	 * @return char* 
+	 */
+	char* (*get_port_descriptor)(unsigned long port_type, unsigned long port_num);
+
+	/**
+	 * @brief Get the port descriptor short object
+	 * 
+	 * @param port_type 
+	 * @param port_num 
+	 * @return char* 
+	 */
+	char* (*get_port_descriptor_short)(unsigned long port_type, 
+			unsigned long port_num);
+
+	/**
+	 * @brief 
+	 * 
+	 */
+	void (*reset_hardware_interface)(void);
+
+	/**
+	 * @brief 
+	 * 
+	 * @return unsigned char 
+	 */
+	unsigned char (*check_critical_error)(void);
+
+	/**
+	 * @brief 
+	 * 
+	 * @return char* 
+	 */
+	char* (*version)(void);
+
+	/**
+	 * @brief Get the dll version object
+	 * 
+	 * @return unsigned short 
+	 */
+	unsigned short (*get_dll_version)(void);
+
+	/**
+	 * @brief 
+	 * 
+	 * @return char* 
+	 */
+	unsigned short (*get_cable_version)(void);
+
+	/**
+	 * @brief Set the debug shift frequency object
+	 * 
+	 * @param shift_speed_in_hz 
+	 */
+	void (*set_debug_shift_frequency)(unsigned long shift_speed_in_hz);
+
+	/**
+	 * @brief Set the reset delay in ms object
+	 * 
+	 * @param delaylength 
+	 */
+	void (*set_reset_delay_in_ms)(unsigned long delay_length);
+
+	/**
+	 * @brief 
+	 * 
+	 * @return true 
+	 * @return false 
+	 */
+	bool (*target_reset)(void);
+
+	/**
+	 * @brief 
+	 * 
+	 * @return true 
+	 * @return false 
+	 */
+	bool (*target_check_if_halted)(void);
+
+	/**
+	 * @brief 
+	 * 
+	 * @return true 
+	 * @return false 
+	 */
+	bool (*target_halt)(void);
+
+	/**
+	 * @brief 
+	 * 
+	 * @return true 
+	 * @return false 
+	 */
+	bool (*target_resume)(void);
+
+	/**
+	 * @brief 
+	 * 
+	 * @return true 
+	 * @return false 
+	 */
+	bool (*target_step)(void);
+
+	/**
+	 * @brief Set the reset pin state object
+	 * 
+	 * @param state 
+	 */
+	void (*set_reset_pin_state)(unsigned char state);
+
+	/**
+	 * @brief 
+	 * 
+	 * @param file_name 
+	 */
+	void (*open_debug_file)(char *file_name);
+
+	/**
+	 * @brief 
+	 * 
+	 * @param file_name 
+	 */
+	void (*close_debug_file)(char *file_name);
+
+	/**
+	 * @brief 
+	 * 
+	 * @param memory_access_tag 
+	 * @param memory_address 
+	 * @param read_result 
+	 * @return unsigned long 
+	 */
+	unsigned long (*read_32bit_value)(unsigned long memory_access_tag, 
+			unsigned long memory_address, ml_mem_result *optional_result);
+
+	/**
+	 * @brief 
+	 * 
+	 * @param memory_access_tag 
+	 * @param memory_address 
+	 * @param memory_data 
+	 * @param write_result 
+	 */
+	void (*write_32bit_value)(unsigned long memory_access_tag,
+			unsigned long memory_address, unsigned long memory_data, 
+			ml_mem_result *optional_result);
+
+	/**
+	 * @brief 
+	 * 
+	 * @param memory_access_tag 
+	 * @param memory_address 
+	 * @param read_result 
+	 * @return unsigned short 
+	 */
+	unsigned short (*read_16bit_value)(unsigned long memory_access_tag,
+			unsigned long memory_address, ml_mem_result *optional_result);
+
+	/**
+	 * @brief 
+	 * 
+	 * @param memory_access_tag 
+	 * @param memory_address 
+	 * @param memory_data 
+	 * @param write_result 
+	 */
+	void (*write_16bit_value)(unsigned long memory_access_tag,
+			unsigned long memory_address, unsigned long memory_data,
+			ml_mem_result *optional_result);
+
+	/**
+	 * @brief 
+	 * 
+	 * @param memory_access_tag 
+	 * @param memory_address 
+	 * @param read_result 
+	 * @return unsigned char 
+	 */
+	unsigned char (*read_8bit_value)(unsigned long memory_access_tag,
+			unsigned long memory_address, ml_mem_result *optional_result);
+
+	/**
+	 * @brief 
+	 * 
+	 * @param memory_access_tag 
+	 * @param memory_address 
+	 * @param memory_data 
+	 * @param write_result 
+	 */
+	void (*write_8bit_value)(unsigned long memory_access_tag,
+			unsigned long memory_address, unsigned long memory_data, 
+			ml_mem_result *optional_result);
+
+	/**
+	 * @brief Get the block object
+	 * 
+	 * @param memory_access_tag 
+	 * @param memory_address 
+	 * @param num_bytes 
+	 * @param access_sizing 
+	 * @param buffer_ptr 
+	 * @param read_result 
+	 * @return true 
+	 * @return false 
+	 */
+	bool (*get_block)(unsigned long memory_access_tag, unsigned long memory_address,
+			unsigned long num_bytes, unsigned long access_sizing, 
+			unsigned char *buffer_ptr, unsigned char *optional_error_ptr);
+
+	/**
+	 * @brief 
+	 * 
+	 * @param memory_access_tag 
+	 * @param memory_address 
+	 * @param num_bytes 
+	 * @param access_sizing 
+	 * @param buffer_ptr 
+	 * @param write_result 
+	 * @return true 
+	 * @return false 
+	 */
+	bool (*put_block)(unsigned long memory_access_tag, unsigned long memory_address,
+			unsigned long num_bytes, unsigned long access_sizing,
+			unsigned char *buffer_ptr, unsigned char *optional_error_ptr);
+
+	/**
+	 * @brief 
+	 * 
+	 * @param file_name 
+	 * @param memory_start_address 
+	 * @return true 
+	 * @return false 
+	 */
+	bool (*load_bin_file)(char *file_name, unsigned long start_address);
+
+	/**
+	 * @brief 
+	 * 
+	 * @param file_name 
+	 * @param memory_start_address 
+	 * @return true 
+	 * @return false 
+	 */
+	bool (*load_srec_file)(char *file_name, unsigned long start_address);
+
+	/**
+	 * @brief Get the mcu register object
+	 * 
+	 * @param register_access_tags 
+	 * @param reg_num 
+	 * @param reg_value 
+	 * @return true 
+	 * @return false 
+	 */
+	bool (*get_mcu_register)(unsigned long register_access_tags, 
+			unsigned long reg_num, unsigned long *reg_value);
+
+	/**
+	 * @brief Set the mcu register object
+	 * 
+	 * @param register_access_tags 
+	 * @param reg_num 
+	 * @param reg_value 
+	 * @return true 
+	 * @return false 
+	 */
+	bool (*set_mcu_register)(unsigned long register_access_tags,
+			unsigned long reg_num, unsigned long reg_value);
+};
+
+struct ml_device_state_s {
+	///
+	const char *port_name;
+	const char *debug_file;
+	unsigned long ref_count;
+	ml_interface_type iface;		//< JTAG as default
+	unsigned long int shift_speed;	//< 1Mhz as a default
+	///
+	bool dll_loaded;
+	void *dll_handle;
+};
+
+/**
+ * @brief 
+ * 
+ * @param usb_handle 
+ * @param dll_file 
+ * @return int 
+ */
+int ml_universal_load_dll(struct ml_device_state_s *device_state, 
+		struct ml_device_api_s *device_api, char *dll_file);
+
+/**
+ * @brief Closes the P&E Universal dynamically linked library
+ * 
+ * @return int 
+ */
+int ml_universal_unload_dll(struct ml_device_state_s *multilink);
+
+#endif // MULTILINK_STATIC_LIBRARY
+#endif // OPENOCD_JTAG_DRIVERS_MULTILINK_UNIVERSAL_H
diff --git a/src/jtag/drivers/opendous.c b/src/jtag/drivers/opendous.c
index 6881959c3..f331c35fb 100644
--- a/src/jtag/drivers/opendous.c
+++ b/src/jtag/drivers/opendous.c
@@ -813,7 +813,7 @@ void opendous_debug_buffer(uint8_t *buffer, int length)
 	int j;
 
 	for (i = 0; i < length; i += BYTES_PER_LINE) {
-		snprintf(line, 5, "%04x", i);
+		snprintf(line, 9, "%04x", i);
 		for (j = i; j < i + BYTES_PER_LINE && j < length; j++) {
 			snprintf(s, 4, " %02x", buffer[j]);
 			strcat(line, s);
diff --git a/src/jtag/drivers/xlnx-pcie-xvc.c b/src/jtag/drivers/xlnx-pcie-xvc.c
index c05b9cf4a..da8ed7faf 100644
--- a/src/jtag/drivers/xlnx-pcie-xvc.c
+++ b/src/jtag/drivers/xlnx-pcie-xvc.c
@@ -229,7 +229,7 @@ static int xlnx_pcie_xvc_execute_pathmove(struct jtag_command *cmd)
 	return ERROR_OK;
 }
 
-static int xlnx_pcie_xvc_execute_scan(struct jtag_command *cmd)
+static int xlnx_pcie_xvc_(struct jtag_command *cmd)
 {
 	enum scan_type type = jtag_scan_type(cmd->cmd.scan);
 	tap_state_t saved_end_state = cmd->cmd.scan->end_state;
diff --git a/src/jtag/hla/hla_layout.c b/src/jtag/hla/hla_layout.c
index 16b221797..aa0eebd9c 100644
--- a/src/jtag/hla/hla_layout.c
+++ b/src/jtag/hla/hla_layout.c
@@ -80,6 +80,14 @@ static const struct hl_layout hl_layouts[] = {
 	 .close = hl_layout_close,
 	 .api = &nulink_usb_layout_api,
 	},
+#endif
+#if BUILD_HLADAPTER_MULTILINK
+	{
+	 .name = "multlink",
+	 .open = hl_layout_open,
+	 .close = hl_layout_close,
+	 .api = &multilink_usb_layout_api,
+	},
 #endif
 	{.name = NULL, /* END OF TABLE */ },
 };
diff --git a/src/jtag/hla/hla_layout.h b/src/jtag/hla/hla_layout.h
index a8088fe95..e86e5a13c 100644
--- a/src/jtag/hla/hla_layout.h
+++ b/src/jtag/hla/hla_layout.h
@@ -32,6 +32,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;
+extern struct hl_layout_api_s multilink_usb_layout_api;
 
 /** */
 struct hl_layout_api_s {
diff --git a/tcl/board/nxp_mcimx7ulp-evk.cfg b/tcl/board/nxp_mcimx7ulp-evk.cfg
new file mode 100644
index 000000000..fe14b9487
--- /dev/null
+++ b/tcl/board/nxp_mcimx7ulp-evk.cfg
@@ -0,0 +1,114 @@
+# NXP IMX7SABRE board
+# use on-board JTAG header
+transport select jtag
+
+# set a safe speed, can be overridden
+adapter speed 1000
+
+# reset configuration has TRST and SRST support
+reset_config trst_and_srst srst_push_pull
+# need at least 100ms delay after SRST release for JTAG
+adapter srst delay 100
+
+# source the target file
+source [find target/imx7.cfg]
+# import mrw proc
+source [find mem_helper.tcl]
+
+# function to disable the on-chip watchdog
+proc imx7_disable_wdog { } {
+    # echo "disable watchdog power-down counter"
+    mwh phys 0x30280008 0x00
+}
+
+proc imx7_uart_dbgconf { } {
+    # disable response to debug_req signal for uart1
+    mww phys 0x308600b4 0x0a60
+}
+
+proc check_bits_set_32 { addr mask } {
+    while { [expr [mrw $addr] & $mask == 0] } { }
+}
+
+proc apply_dcd { } {
+    # echo "apply dcd"
+
+    mww phys 0x30340004 0x4F400005
+    # Clear then set bit30 to ensure exit from DDR retention
+    mww phys 0x30360388 0x40000000
+    mww phys 0x30360384 0x40000000
+
+    mww phys 0x30391000 0x00000002
+    mww phys 0x307a0000 0x01040001
+    mww phys 0x307a01a0 0x80400003
+    mww phys 0x307a01a4 0x00100020
+    mww phys 0x307a01a8 0x80100004
+    mww phys 0x307a0064 0x00400046
+    mww phys 0x307a0490 0x00000001
+    mww phys 0x307a00d0 0x00020083
+    mww phys 0x307a00d4 0x00690000
+    mww phys 0x307a00dc 0x09300004
+    mww phys 0x307a00e0 0x04080000
+    mww phys 0x307a00e4 0x00100004
+    mww phys 0x307a00f4 0x0000033f
+    mww phys 0x307a0100 0x09081109
+    mww phys 0x307a0104 0x0007020d
+    mww phys 0x307a0108 0x03040407
+    mww phys 0x307a010c 0x00002006
+    mww phys 0x307a0110 0x04020205
+    mww phys 0x307a0114 0x03030202
+    mww phys 0x307a0120 0x00000803
+    mww phys 0x307a0180 0x00800020
+    mww phys 0x307a0184 0x02000100
+    mww phys 0x307a0190 0x02098204
+    mww phys 0x307a0194 0x00030303
+    mww phys 0x307a0200 0x00000016
+    mww phys 0x307a0204 0x00171717
+    mww phys 0x307a0214 0x04040404
+    mww phys 0x307a0218 0x0f040404
+    mww phys 0x307a0240 0x06000604
+    mww phys 0x307a0244 0x00000001
+    mww phys 0x30391000 0x00000000
+    mww phys 0x30790000 0x17420f40
+    mww phys 0x30790004 0x10210100
+    mww phys 0x30790010 0x00060807
+    mww phys 0x307900b0 0x1010007e
+    mww phys 0x3079009c 0x00000d6e
+    mww phys 0x30790020 0x08080808
+    mww phys 0x30790030 0x08080808
+    mww phys 0x30790050 0x01000010
+    mww phys 0x30790050 0x00000010
+
+    mww phys 0x307900c0 0x0e407304
+    mww phys 0x307900c0 0x0e447304
+    mww phys 0x307900c0 0x0e447306
+
+    check_bits_set_32 0x307900c4 0x1
+
+    mww phys 0x307900c0 0x0e447304
+    mww phys 0x307900c0 0x0e407304
+
+
+    mww phys 0x30384130 0x00000000
+    mww phys 0x30340020 0x00000178
+    mww phys 0x30384130 0x00000002
+    mww phys 0x30790018 0x0000000f
+
+    check_bits_set_32 0x307a0004 0x1
+}
+
+# disable internal reset-assert handling to
+# allow reset-init to work
+$_TARGETNAME.0 configure -event reset-assert ""
+$_TARGETNAME.1 configure -event reset-assert ""
+$_TARGETNAME_2 configure -event reset-assert ""
+
+$_TARGETNAME.0 configure -event reset-init {
+    global _CHIPNAME
+    imx7_disable_wdog
+    imx7_uart_dbgconf
+    # apply_dcd
+    $_CHIPNAME.dap memaccess 0
+}
+
+target smp $_TARGETNAME.0 $_TARGETNAME.1
diff --git a/tcl/board/nxp_mcimx7ulp-evk_auto.cfg b/tcl/board/nxp_mcimx7ulp-evk_auto.cfg
new file mode 100644
index 000000000..f5060df27
--- /dev/null
+++ b/tcl/board/nxp_mcimx7ulp-evk_auto.cfg
@@ -0,0 +1,16 @@
+# NXP IMX7SABRE board
+# use on-board JTAG header
+transport select jtag
+
+# set a safe speed, can be overridden
+adapter speed 1000
+
+# reset configuration has TRST and SRST support
+reset_config trst_and_srst
+# need at least 100ms delay after SRST release for JTAG
+adapter srst delay 100
+
+# source the target file
+#source [find target/imx7.cfg]
+
+jtag_rclk 8
diff --git a/tcl/interface/multilink.cfg b/tcl/interface/multilink.cfg
new file mode 100644
index 000000000..c682b8178
--- /dev/null
+++ b/tcl/interface/multilink.cfg
@@ -0,0 +1,12 @@
+#
+# PRmicro Multilink Debug Probes
+#
+# https://www.pemicro.com/multilink/
+#
+adapter driver hla
+hla_layout multilink
+hla_device_desc "Multilink"
+hla_vid_pid 0x1357 0x0503
+
+# Only swd is supported
+transport select hla_swd
diff --git a/tcl/target/imx7ulp.cfg b/tcl/target/imx7ulp.cfg
index 879fcf8cc..d950196ac 100644
--- a/tcl/target/imx7ulp.cfg
+++ b/tcl/target/imx7ulp.cfg
@@ -13,17 +13,17 @@ if { [info exists DAP_TAPID] } {
     set _DAP_TAPID $DAP_TAPID
 } else {
     # TAPID is from FreeScale!
-    set _DAP_TAPID 0x188e101d
+    set _DAP_TAPID 0x5ba00477
 }
 
 jtag newtap $_CHIPNAME cpu -irlen 4 -ircapture 0x01 -irmask 0x0f \
-        -expected-id $_DAP_TAPID
+    -expected-id $_DAP_TAPID
 
 dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu
 
 # Cortex-A7
 target create $_CHIPNAME.cpu_a7 cortex_a -dap $_CHIPNAME.dap \
-        -coreid 0 -dbgbase 0x80030000
+    -coreid 0 -dbgbase 0x80030000
 
 # Cortex-M4
 # Boots by default so don't defer examination
diff --git a/workspace.code-workspace b/workspace.code-workspace
new file mode 100644
index 000000000..f7efdde21
--- /dev/null
+++ b/workspace.code-workspace
@@ -0,0 +1,25 @@
+{
+	"folders": [
+		{
+			"path": "."
+		}
+	],
+	"settings": {
+		"makefile.extensionOutputFolder": "./.vscode",
+		"files.associations": {
+			"*.cps": "javascript",
+			"*.txt": "cmake",
+			"*.h": "c",
+			"*.ipp": "c",
+			"chrono": "c",
+			"coroutine": "c",
+			"cstddef": "c",
+			"functional": "c",
+			"*.txx": "c",
+			"complex": "c",
+			"core": "c",
+			"algorithm": "c",
+			"cmath": "c"
+		}
+	}
+}
\ No newline at end of file


Reply via email to