This is an automated email from Gerrit. "Richard Pasek <rpa...@google.com>" just uploaded a new patch set to Gerrit, which you can find at https://review.openocd.org/c/openocd/+/8645
-- gerrit commit 4343e856172deb6d8f50e4d9e144f575b31e655d Author: Richard Pasek <rpa...@google.com> Date: Wed Dec 11 00:43:57 2024 -0500 Add Linux SPI device SWD adapter support To alleviate the need to bitbang SWD, I've written a SWD SPI implementation. This code is inspired by the work of lu...@appkaki.com as shown at github.com/lupyuen/openocd-spi but with the desire to be more generic. This implementation makes use of the more common 4 wire SPI port using full duplex transfers to be able to capture the SWD ACK bits when a SWD TX operation is in progress. Change-Id: Ic2f38a1806085d527e6f999a3d15aea6f32d1019 Signed-off-by: rpa...@google.com diff --git a/configure.ac b/configure.ac index 567152b0a6..8adf178204 100644 --- a/configure.ac +++ b/configure.ac @@ -163,6 +163,9 @@ m4_define([PCIE_ADAPTERS], m4_define([SERIAL_PORT_ADAPTERS], [[[buspirate], [Bus Pirate], [BUS_PIRATE]]]) +m4_define([LINUXSPIDEV_ADAPTER], + [[[linuxspidev], [Linux SPI DEV driver], [LINUXSPIDEV]]]) + # The word 'Adapter' in "Dummy Adapter" below must begin with a capital letter # because there is an M4 macro called 'adapter'. m4_define([DUMMY_ADAPTER], @@ -294,6 +297,8 @@ AC_ARG_ADAPTERS([ LIBJAYLINK_ADAPTERS ],[auto]) +AC_ARG_ADAPTERS([LINUXSPIDEV_ADAPTER],[no]) + AC_ARG_ADAPTERS([DUMMY_ADAPTER],[no]) AC_ARG_ENABLE([parport], @@ -367,6 +372,10 @@ AC_ARG_ENABLE([sysfsgpio], AS_HELP_STRING([--enable-sysfsgpio], [Enable building support for programming driven via sysfs gpios.]), [build_sysfsgpio=$enableval], [build_sysfsgpio=no]) +AC_ARG_ENABLE([linuxspidev], + AS_HELP_STRING([--enable-linuxspidev], [Enable building support for SWD via Linux SPI DEV]), + [build_linuxspidev=$enableval], [build_linuxspidev=no]) + AS_CASE([$host_os], [linux*], [ is_linux=yes @@ -376,6 +385,10 @@ AS_CASE([$host_os], AC_MSG_ERROR([sysfsgpio is only available on linux]) ]) + AS_IF([test "x$build_linuxspidev" = "xyes"], [ + AC_MSG_ERROR([linuxspidev is only available on linux]) + ]) + AS_CASE([$host_os], [freebsd*], [], [ AS_IF([test "x$build_rshim" = "xyes"], [ @@ -632,6 +645,13 @@ AS_IF([test "x$build_sysfsgpio" = "xyes"], [ AC_DEFINE([BUILD_SYSFSGPIO], [0], [0 if you don't want SysfsGPIO driver.]) ]) +AS_IF([test "x$build_linuxspidev" != "xno"], [ + AC_DEFINE([BUILD_LINUXSPIDEV], [1], [1 if you want the Linux SPI DEV driver.]) +], [ + AC_DEFINE([BUILD_LINUXSPIDEV], [0], [0 if you don't want the Linux SPI DEV driver.]) +]) + + PKG_CHECK_MODULES([LIBUSB1], [libusb-1.0], [ use_libusb1=yes AC_DEFINE([HAVE_LIBUSB1], [1], [Define if you have libusb-1.x]) @@ -727,6 +747,7 @@ PROCESS_ADAPTERS([LIBJAYLINK_ADAPTERS], ["x$use_internal_libjaylink" = "xyes" -o PROCESS_ADAPTERS([PCIE_ADAPTERS], ["x$is_linux" = "xyes"], [Linux build]) PROCESS_ADAPTERS([SERIAL_PORT_ADAPTERS], ["x$can_build_buspirate" = "xyes"], [internal error: validation should happen beforehand]) +PROCESS_ADAPTERS([LINUXSPIDEV_ADAPTER], [true], [unused]) PROCESS_ADAPTERS([DUMMY_ADAPTER], [true], [unused]) AS_IF([test "x$enable_linuxgpiod" != "xno"], [ @@ -782,6 +803,7 @@ AM_CONDITIONAL([AMTJTAGACCEL], [test "x$build_amtjtagaccel" = "xyes"]) AM_CONDITIONAL([GW16012], [test "x$build_gw16012" = "xyes"]) AM_CONDITIONAL([REMOTE_BITBANG], [test "x$build_remote_bitbang" = "xyes"]) AM_CONDITIONAL([SYSFSGPIO], [test "x$build_sysfsgpio" = "xyes"]) +AM_CONDITIONAL([LINUXSPIDEV], [test "x$build_linuxspidev" = "xyes"]) AM_CONDITIONAL([USE_LIBUSB1], [test "x$use_libusb1" = "xyes"]) AM_CONDITIONAL([IS_CYGWIN], [test "x$is_cygwin" = "xyes"]) AM_CONDITIONAL([IS_MINGW], [test "x$is_mingw" = "xyes"]) @@ -876,6 +898,7 @@ m4_foreach([adapter], [USB1_ADAPTERS, LIBFTDI_USB1_ADAPTERS, LIBGPIOD_ADAPTERS, LIBJAYLINK_ADAPTERS, PCIE_ADAPTERS, SERIAL_PORT_ADAPTERS, + LINUXSPIDEV_ADAPTER, DUMMY_ADAPTER, OPTIONAL_LIBRARIES, COVERAGE], diff --git a/src/jtag/drivers/Makefile.am b/src/jtag/drivers/Makefile.am index 8be834859c..b07f156b04 100644 --- a/src/jtag/drivers/Makefile.am +++ b/src/jtag/drivers/Makefile.am @@ -173,6 +173,9 @@ endif if SYSFSGPIO DRIVERFILES += %D%/sysfsgpio.c endif +if LINUXSPIDEV +DRIVERFILES += %D%/linuxspidev.c +endif if XLNX_PCIE_XVC DRIVERFILES += %D%/xlnx-pcie-xvc.c endif diff --git a/src/jtag/drivers/linuxspidev.c b/src/jtag/drivers/linuxspidev.c new file mode 100644 index 0000000000..4480929ed5 --- /dev/null +++ b/src/jtag/drivers/linuxspidev.c @@ -0,0 +1,542 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/*************************************************************************** + * Copyright (C) 2020 by Lup Yuen Lee * + * lu...@appkaki.com * + * * + * Copyright (C) 2024 by Richard Pasek * + * rpa...@google.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/>. * + ***************************************************************************/ + + /** + * @file + * Implementation of SWD protocol with a Linux SPI device. + * + * Full duplex SPI transactions are used to read and write from the client at + * the same time. + * + * Electrical connections: + * +--------------+ +--------------+ + * | | 1K | | + * | MOSI|---/\/\/\---+ | | + * | Host | | | Client | + * | MISO|------------+--------|SWDIO | + * | | | | + * | SCK|---------------------|SWDCLK | + * | | | | + * +--------------+ +--------------+ + * + * The 1K resistor works well with most MCUs up to 3 MHz. A lower resistance + * could be used to achieve higher speeds granted that the client SWDIO pin has + * enough drive strength to pull the signal high while being pulled low by this + * resistor + * + * openocd.cfg example: + * adapter driver linuxspidev + * spidev path "/dev/spidev1.0" + * spidev mode 3 + */ + + // #define LOG_SPI_EXCHANGE // Uncomment to log SPI exchanges (very verbose) + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdint.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/types.h> +#include <linux/spi/spidev.h> +#include <jtag/swd.h> +#include <jtag/interface.h> +#include <jtag/commands.h> +#include <helper/time_support.h> + +/* Timeout for retrying on SWD WAIT in msec */ +#define SWD_WAIT_TIMEOUT 500 + +/* LSB to MSB buffer sizes */ +#define MAX_SPI_EXCH_SIZE 32 + +#define CMD_BITS 8 +#define TURN_BITS 1 +#define ACK_BITS 3 +#define DATA_BITS 32 +#define PARITY_BITS 1 + +#define SWD_TX_BYTES (DIV_ROUND_UP(CMD_BITS + TURN_BITS + ACK_BITS + TURN_BITS + DATA_BITS + PARITY_BITS, 8)) +#define SWD_RX_BYTES (DIV_ROUND_UP(CMD_BITS + TURN_BITS + ACK_BITS + DATA_BITS + PARITY_BITS + TURN_BITS, 8)) + +/// File descriptor for SPI device +static int spi_fd = -1; + +static int queued_retval; + +static int spidev_swd_switch_seq(enum swd_special_seq seq); +static void spidev_swd_write_reg(uint8_t cmd, uint32_t value, uint32_t ap_delay_clk); + +// SPI Configuration +static char *spi_path; +static uint32_t spi_mode = SPI_MODE_3; // Note: SPI in LSB mode is not often supported. We'll flip LSB to MSB ourselves. +static uint32_t speed_khz = 3000; +static const uint8_t spi_bits = 8; +static const uint16_t delay = 0; + +static void spi_exchange(const uint8_t *txbuf, uint8_t *rxbuf, unsigned int len) +{ + static uint8_t tx_buf[MAX_SPI_EXCH_SIZE]; + static uint8_t rx_buf[MAX_SPI_EXCH_SIZE]; + +#ifdef LOG_SPI_EXCHANGE + LOG_OUTPUT("spi_exchange: len=%d", len); +#endif // LOG_SPI_EXCHANGE + + if (len == 0 || (!txbuf && !rxbuf)) { + LOG_DEBUG("exchange with no length len=%d ", len); + return; + } + + if (len >= MAX_SPI_EXCH_SIZE) { + LOG_ERROR("exchange too large len=%d ", len); + return; + } + + if (txbuf) { + // Reverse LSB to MSB + for (unsigned int i = 0; i < len; i++) { + tx_buf[i] = flip_u32(txbuf[i], 8); + } +#ifdef LOG_SPI_EXCHANGE + if (len != 0) { + LOG_OUTPUT(" tx data="); + for (unsigned int i = 0; i < len; i++) { + LOG_OUTPUT("%.2X ", tx_buf[i]); + } + } + LOG_OUTPUT("\n"); + +#endif // LOG_SPI_EXCHANGE + } + // Transmit the MSB buffer to SPI device. + struct spi_ioc_transfer tr = { + .tx_buf = (__u64)(txbuf ? tx_buf : NULL), + .rx_buf = (__u64)(rxbuf ? rx_buf : NULL), + .len = len, + .delay_usecs = delay, + .speed_hz = speed_khz * 1000, + .bits_per_word = spi_bits, + }; + int ret = ioctl(spi_fd, SPI_IOC_MESSAGE(1), &tr); + + // Check SPI result. + if (ret < 1) { + LOG_ERROR("exchange failed"); + return; + } + + if (rxbuf) { + // Reverse MSB to LSB + for (unsigned int i = 0; i < len; i++) { + rxbuf[i] = flip_u32(rx_buf[i], 8); + } +#ifdef LOG_SPI_EXCHANGE + if (len != 0) { + LOG_OUTPUT(" rx data="); + } + for (unsigned int i = 0; i < len; i++) { + LOG_OUTPUT("%.2X ", rxbuf[i]); + } + puts(""); + +#endif // LOG_SPI_EXCHANGE + } +} + +static void spi_delay(unsigned int cycles) +{ + const uint8_t zero = 0; + + for (unsigned int i = 0; i < DIV_ROUND_UP(cycles, 8); i++) { + spi_exchange(&zero, NULL, sizeof(zero)); + } +} + +/// Set JTAG speed (not used) +static int spidev_khz(int khz, int *jtag_speed) +{ + // TODO + if (!khz) { + LOG_DEBUG("RCLK not supported"); + return ERROR_FAIL; + } + *jtag_speed = 0; + return ERROR_OK; +} + +/// Set speed div +static int spidev_speed_div(int speed, int *khz) +{ + *khz = speed_khz; + return ERROR_OK; +} + +/// Set JTAG delay (not used) +static int spidev_speed(int speed) +{ + return ERROR_OK; +} + +/// Is SWD transport supported +static bool spidev_swd_mode_possible(void) +{ + return 1; +} + +/// Init driver +static int spidev_init(void) +{ + LOG_INFO("SPI SWD driver"); + + if (spidev_swd_mode_possible()) { + LOG_INFO("SWD only mode enabled"); + } else { + LOG_ERROR("Mode not supported"); + return ERROR_JTAG_INIT_FAILED; + } + + if (spi_fd >= 0) + return ERROR_OK; + + // Open SPI device. + spi_fd = open(spi_path, O_RDWR); + if (spi_fd < 0) { + LOG_ERROR("Failed to open SPI port at %s", spi_path); + return ERROR_JTAG_INIT_FAILED; + } + + // Set SPI mode. + int ret = ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode); + if (ret == -1) { + LOG_ERROR("Failed to set SPI mode %d", spi_mode); + return ERROR_JTAG_INIT_FAILED; + } + + // Set SPI bits per word. + ret = ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits); + if (ret == -1) { + LOG_ERROR("Failed to set SPI %d bits per transfer", spi_bits); + return ERROR_JTAG_INIT_FAILED; + } + + // Set SPI read and write max speed. + uint32_t speed = speed_khz * 1000; + ret = ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); + if (ret == -1) { + LOG_ERROR("Failed to set SPI %u max speed", speed); + return ERROR_JTAG_INIT_FAILED; + } + + spidev_swd_switch_seq(JTAG_TO_SWD); + + LOG_INFO("Opened SPI device at %s in mode %d with %d bits per transfer at %u kHz", spi_path, spi_mode, spi_bits, speed_khz); + + return ERROR_OK; +} + +/// Terminate driver +static int spidev_quit(void) +{ + free(spi_path); + + if (spi_fd < 0) + return ERROR_OK; + + close(spi_fd); + spi_fd = -1; + return ERROR_OK; +} + +static void swd_clear_sticky_errors(void) +{ + spidev_swd_write_reg(swd_cmd(false, false, DP_ABORT), + STKCMPCLR | STKERRCLR | WDERRCLR | ORUNERRCLR, 0); +} + +static int spidev_swd_init(void) +{ + LOG_DEBUG("spidev_swd_init"); + return ERROR_OK; +} + +static int spidev_swd_run_queue(void) +{ + /* A transaction must be followed by another transaction or at least 8 idle cycles to + * ensure that data is clocked through the AP. */ + spi_delay(8); + + int retval = queued_retval; + queued_retval = ERROR_OK; + LOG_DEBUG_IO("SWD queue return value: %02x", retval); + return retval; +} + +static void spidev_swd_read_reg(uint8_t cmd, uint32_t *value, uint32_t ap_delay_clk) +{ + assert(cmd & SWD_CMD_RNW); + + if (queued_retval != ERROR_OK) { + LOG_DEBUG("Skip spidev_swd_read_reg because queued_retval=%d", queued_retval); + return; + } + + int64_t timeout = timeval_ms() + SWD_WAIT_TIMEOUT; + for (unsigned int retry = 0;; retry++) { + uint8_t cmd_trn_ack_data_parity_trn_tx[SWD_RX_BYTES] = { 0 }; + uint8_t cmd_trn_ack_data_parity_trn_rx[SWD_RX_BYTES] = { 0 }; + + cmd |= SWD_CMD_START | SWD_CMD_PARK; + buf_set_u32(cmd_trn_ack_data_parity_trn_tx, 0, CMD_BITS, cmd); + + spi_exchange(cmd_trn_ack_data_parity_trn_tx, cmd_trn_ack_data_parity_trn_rx, SWD_RX_BYTES); + + int ack = buf_get_u32(cmd_trn_ack_data_parity_trn_rx, CMD_BITS + TURN_BITS, ACK_BITS); + uint32_t data = buf_get_u32(cmd_trn_ack_data_parity_trn_rx, CMD_BITS + TURN_BITS + ACK_BITS, DATA_BITS); + int parity = buf_get_u32(cmd_trn_ack_data_parity_trn_rx, CMD_BITS + TURN_BITS + ACK_BITS + DATA_BITS, PARITY_BITS); + + LOG_CUSTOM_LEVEL((ack != SWD_ACK_OK && (retry == 0 || ack != SWD_ACK_WAIT)) + ? LOG_LVL_DEBUG + : LOG_LVL_DEBUG_IO, + "%s %s read reg %X = %08" PRIx32, + ack == SWD_ACK_OK ? "OK" : + ack == SWD_ACK_WAIT ? "WAIT" : + ack == SWD_ACK_FAULT ? "FAULT" : "JUNK", + cmd & SWD_CMD_APNDP ? "AP" : "DP", + (cmd & SWD_CMD_A32) >> 1, + data); + + if (ack == SWD_ACK_WAIT && timeval_ms() <= timeout) { + swd_clear_sticky_errors(); + if (retry > 20) + alive_sleep(1); + + continue; + } + + if (retry > 1) + LOG_DEBUG("SWD WAIT: retried %u times", retry); + + if (ack != SWD_ACK_OK) { + queued_retval = swd_ack_to_error_code(ack); + return; + } + + if (parity != parity_u32(data)) { + LOG_ERROR("Wrong parity detected"); + queued_retval = ERROR_FAIL; + return; + } + + if (value) + *value = data; + + if (cmd & SWD_CMD_APNDP) + spi_delay(ap_delay_clk); + return; + } +} + +static void spidev_swd_write_reg(uint8_t cmd, uint32_t value, uint32_t ap_delay_clk) +{ + assert(!(cmd & SWD_CMD_RNW)); + + if (queued_retval != ERROR_OK) { + LOG_DEBUG("Skip spidev_swd_write_reg because queued_retval=%d", queued_retval); + return; + } + + int64_t timeout = timeval_ms() + SWD_WAIT_TIMEOUT; + + /* Devices do not reply to DP_TARGETSEL write cmd, ignore received ack */ + bool check_ack = swd_cmd_returns_ack(cmd); + + /* init the array to silence scan-build */ + uint8_t cmd_trn_ack_data_parity_trn_tx[SWD_TX_BYTES] = { 0 }; + uint8_t cmd_trn_ack_data_parity_trn_rx[SWD_TX_BYTES] = { 0 }; + for (unsigned int retry = 0;; retry++) { + + cmd |= SWD_CMD_START | SWD_CMD_PARK; + + buf_set_u32(cmd_trn_ack_data_parity_trn_tx, 0, CMD_BITS, cmd); + buf_set_u32(cmd_trn_ack_data_parity_trn_tx, CMD_BITS + TURN_BITS + ACK_BITS + TURN_BITS, DATA_BITS, value); + buf_set_u32(cmd_trn_ack_data_parity_trn_tx, CMD_BITS + TURN_BITS + ACK_BITS + TURN_BITS + DATA_BITS, PARITY_BITS, parity_u32(value)); + + spi_exchange(cmd_trn_ack_data_parity_trn_tx, cmd_trn_ack_data_parity_trn_rx, SWD_TX_BYTES); + + int ack = buf_get_u32(cmd_trn_ack_data_parity_trn_rx, CMD_BITS + TURN_BITS, ACK_BITS); + LOG_CUSTOM_LEVEL((check_ack && ack != SWD_ACK_OK && (retry == 0 || ack != SWD_ACK_WAIT)) + ? LOG_LVL_DEBUG + : LOG_LVL_DEBUG_IO, + "%s%s %s write reg %X = %08" PRIx32, + check_ack ? "" : "ack ignored ", + ack == SWD_ACK_OK ? "OK" : + ack == SWD_ACK_WAIT ? "WAIT" : + ack == SWD_ACK_FAULT ? "FAULT" : "JUNK", + cmd & SWD_CMD_APNDP ? "AP" : "DP", + (cmd & SWD_CMD_A32) >> 1, + buf_get_u32(cmd_trn_ack_data_parity_trn_tx, CMD_BITS + TURN_BITS + ACK_BITS + TURN_BITS, DATA_BITS)); + + if (check_ack && ack == SWD_ACK_WAIT && timeval_ms() <= timeout) { + swd_clear_sticky_errors(); + if (retry > 20) + alive_sleep(1); + + continue; + } + + if (retry > 1) + LOG_DEBUG("SWD WAIT: retried %u times", retry); + + if (check_ack && ack != SWD_ACK_OK) { + queued_retval = swd_ack_to_error_code(ack); + return; + } + + if (cmd & SWD_CMD_APNDP) + spi_delay(ap_delay_clk); + + return; + } +} + +static int spidev_swd_switch_seq(enum swd_special_seq seq) +{ + switch (seq) { + case LINE_RESET: + LOG_DEBUG_IO("SWD line reset"); + spi_exchange(swd_seq_line_reset, NULL, swd_seq_line_reset_len / 8); + break; + case JTAG_TO_SWD: + LOG_DEBUG("JTAG-to-SWD"); + spi_exchange(swd_seq_jtag_to_swd, NULL, swd_seq_jtag_to_swd_len / 8); + break; + case JTAG_TO_DORMANT: + LOG_DEBUG("JTAG-to-DORMANT"); + spi_exchange(swd_seq_jtag_to_dormant, NULL, swd_seq_jtag_to_dormant_len / 8); + break; + case SWD_TO_JTAG: + LOG_DEBUG("SWD-to-JTAG"); + spi_exchange(swd_seq_swd_to_jtag, NULL, swd_seq_swd_to_jtag_len / 8); + break; + case SWD_TO_DORMANT: + LOG_DEBUG("SWD-to-DORMANT"); + spi_exchange(swd_seq_swd_to_dormant, NULL, swd_seq_swd_to_dormant_len / 8); + break; + case DORMANT_TO_SWD: + LOG_DEBUG("DORMANT-to-SWD"); + spi_exchange(swd_seq_dormant_to_swd, NULL, swd_seq_dormant_to_swd_len / 8); + break; + case DORMANT_TO_JTAG: + LOG_DEBUG("DORMANT-to-JTAG"); + spi_exchange(swd_seq_dormant_to_jtag, NULL, swd_seq_dormant_to_jtag_len / 8); + break; + default: + LOG_ERROR("Sequence %d not supported", seq); + return ERROR_FAIL; + } + + return ERROR_OK; +} + +COMMAND_HANDLER(spidev_handle_path_command) +{ + if (CMD_ARGC == 1) { + free(spi_path); + spi_path = strdup(CMD_ARGV[0]); + } else { + LOG_ERROR("expected exactly one argument to spidev path <path>"); + } + + return ERROR_OK; +} + +COMMAND_HANDLER(spidev_handle_mode_command) +{ + if (CMD_ARGC == 1) + COMMAND_PARSE_NUMBER(u32, CMD_ARGV[0], spi_mode); + else + return ERROR_COMMAND_SYNTAX_ERROR; + + return ERROR_OK; +} + +const struct swd_driver spidev_swd = { + .init = spidev_swd_init, + .switch_seq = spidev_swd_switch_seq, + .read_reg = spidev_swd_read_reg, + .write_reg = spidev_swd_write_reg, + .run = spidev_swd_run_queue, +}; + + +static const struct command_registration spidev_subcommand_handlers[] = { + { + .name = "path", + .handler = &spidev_handle_path_command, + .mode = COMMAND_CONFIG, + .help = "set the path to the spidev device", + .usage = "example: /dev/spidev0.0", + }, + { + .name = "mode", + .handler = &spidev_handle_mode_command, + .mode = COMMAND_CONFIG, + .help = "set the mode of the spi port", + .usage = "(number in decimal)", + }, +}; + +static const struct command_registration spidev_command_handlers[] = { + { + .name = "spidev", + .mode = COMMAND_ANY, + .help = "perform spidev management", + .chain = spidev_subcommand_handlers, + .usage = "", + }, + COMMAND_REGISTRATION_DONE +}; + +/// Only SWD transport supported +static const char *const spidev_transports[] = { "swd", NULL }; + +struct adapter_driver linuxspidev_adapter_driver = { + .name = "linuxspidev", + .transports = spidev_transports, + .commands = spidev_command_handlers, + + .init = spidev_init, + .quit = spidev_quit, + .speed = spidev_speed, + .khz = spidev_khz, + .speed_div = spidev_speed_div, + + .swd_ops = &spidev_swd, +}; diff --git a/src/jtag/interface.h b/src/jtag/interface.h index b448851dc4..f69326492a 100644 --- a/src/jtag/interface.h +++ b/src/jtag/interface.h @@ -386,6 +386,7 @@ extern struct adapter_driver jtag_dpi_adapter_driver; extern struct adapter_driver jtag_vpi_adapter_driver; extern struct adapter_driver kitprog_adapter_driver; extern struct adapter_driver linuxgpiod_adapter_driver; +extern struct adapter_driver linuxspidev_adapter_driver; extern struct adapter_driver opendous_adapter_driver; extern struct adapter_driver openjtag_adapter_driver; extern struct adapter_driver osbdm_adapter_driver; diff --git a/src/jtag/interfaces.c b/src/jtag/interfaces.c index 67f0838e39..e49bd9e0f3 100644 --- a/src/jtag/interfaces.c +++ b/src/jtag/interfaces.c @@ -123,6 +123,9 @@ struct adapter_driver *adapter_drivers[] = { #if BUILD_LINUXGPIOD == 1 &linuxgpiod_adapter_driver, #endif +#if BUILD_LINUXSPIDEV == 1 + &linuxspidev_adapter_driver, +#endif #if BUILD_XLNX_PCIE_XVC == 1 &xlnx_pcie_xvc_adapter_driver, #endif --