From 40b0b104d6967ca75929706996b3dede304b9b25 Mon Sep 17 00:00:00 2001
From: Joerg Wunsch <j@uriah.heep.sax.de>
Date: Sat, 19 Sep 2020 21:32:38 +0000
Subject: [PATCH] patch #9816: Implement new programmer type: linuxspi *
 linuxspi.c: (New file.) * linuxspi.h: (New file.) * Makefile.am: Add new
 files * configure.ac: Add "linuxspi" --enable option * avrdude.conf.in: Add
 "linuxspi" programmer template * pgm_type.c: Include linuxspi programmer *
 doc/avrdude.texi: Document new programmer * avrdude.1: (Dito.)

Submitted by Ralf Ramsauer



git-svn-id: svn://svn.savannah.nongnu.org/avrdude/trunk/avrdude@1447 81a1dc3b-b13d-400b-aceb-764788c761c2
---
 ChangeLog        |  13 ++
 Makefile.am      |   2 +
 NEWS             |   2 +
 avrdude.1        |  32 ++++-
 avrdude.conf.in  |  17 +++
 configure.ac     |  26 ++++
 doc/avrdude.texi |  27 +++++
 linuxspi.c       | 302 +++++++++++++++++++++++++++++++++++++++++++++++
 linuxspi.h       |  35 ++++++
 pgm_type.c       |   2 +
 10 files changed, 456 insertions(+), 2 deletions(-)
 create mode 100644 linuxspi.c
 create mode 100644 linuxspi.h

diff --git a/ChangeLog b/ChangeLog
index 3239724c..5cb5a8cc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2020-09-19  Joerg Wunsch <j.gnu@uriah.heep.sax.de>
+
+	Submitted by Ralf Ramsauer
+	patch #9816: Implement new programmer type: linuxspi
+	* linuxspi.c: (New file.)
+	* linuxspi.h: (New file.)
+	* Makefile.am: Add new files
+	* configure.ac: Add "linuxspi" --enable option
+	* avrdude.conf.in: Add "linuxspi" programmer template
+	* pgm_type.c: Include linuxspi programmer
+	* doc/avrdude.texi: Document new programmer
+	* avrdude.1: (Dito.)
+
 2020-09-18  Joerg Wunsch <j.gnu@uriah.heep.sax.de>
 
 	Submitted by Marcin Miskiewic
diff --git a/Makefile.am b/Makefile.am
index c8d568c9..b9929897 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -149,6 +149,8 @@ libavrdude_a_SOURCES = \
 	libavrdude.h \
 	linuxgpio.c \
 	linuxgpio.h \
+	linuxspi.c \
+	linuxspi.h \
 	linux_ppdev.h \
 	lists.c \
 	my_ddk_hidsdi.h \
diff --git a/NEWS b/NEWS
index 5951533f..98b22788 100644
--- a/NEWS
+++ b/NEWS
@@ -38,6 +38,7 @@ Current:
     - XplainedMini in UPDI mode
     - JTAGICE3 in UPDI mode
     - Atmel Powerdebugger in all modes (JTAG, PDI, UPDI, debugWIRE, ISP)
+    - linuxspi (direct SPI bus e.g. on Raspberry Pi devices)
 
   * Bugfixes:
     bug #47550: Linux GPIO broken
@@ -76,6 +77,7 @@ Current:
     patch #9732: usbtiny_paged_load overflows buffer e.g. when reading EEPROM
     patch #9966: Add JTAGICE3 in UPDI mode
     patch #9963: UsbAsp 3 MHz patch for UsbAsp-flash firmware
+    patch #9816: Implement new programmer type: linuxspi
 
   * Internals:
     - New avrdude.conf keyword "family_id", used to verify SIB attributes
diff --git a/avrdude.1 b/avrdude.1
index 6d805123..1518b8c6 100644
--- a/avrdude.1
+++ b/avrdude.1
@@ -1,6 +1,6 @@
 .\"
 .\" avrdude - A Downloader/Uploader for AVR device programmers
-.\" Copyright (C) 2001, 2002, 2003, 2005 - 2016  Joerg Wunsch
+.\" Copyright (C) 2001, 2002, 2003, 2005 - 2020  Joerg Wunsch
 .\"
 .\" 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
@@ -18,7 +18,7 @@
 .\"
 .\" $Id$
 .\"
-.Dd DATE February 15, 2016
+.Dd DATE September 19, 2020
 .Os
 .Dt AVRDUDE 1
 .Sh NAME
@@ -109,6 +109,34 @@ some resistors in series or better yet use a 3-state buffer driver like
 the 74HC244. Have a look at http://kolev.info/avrdude-linuxgpio for a more
 detailed tutorial about using this programmer type.
 .Pp
+Under a Linux installation with direct access to the SPI bus and GPIO
+pins, such as would be found on a Raspberry Pi, the ``linuxspi''
+programmer type can be used to directly connect to and program a chip
+using the built in interfaces on the computer. The requirements to use
+this type are that an SPI interface is exposed along with one GPIO
+pin. The GPIO serves as the reset output since the Linux SPI drivers
+do not hold slave select down when a transfer is not occuring and thus
+it cannot be used as the reset pin. A readily available level
+translator should be used between the SPI bus/reset GPIO and the chip
+to avoid potentially damaging the computer's SPI controller in the
+event that the chip is running at 5V and the SPI runs at 3.3V. The
+GPIO chosen for reset can be configured in the avrdude configuration
+file using the
+.Li reset
+entry under the linuxspi programmer, or
+directly in the port specification. An external pull-up resistor
+should be connected between the AVR's reset pin and Vcc. If Vcc is not
+the same as the SPI voltage, this should be done on the AVR side of
+the level translator to protect the hardware from damage.
+.Pp
+A commented-out template for this programmer is provided in the
+avrdude configuration file.  To use it, clone that entry into the
+per-user configuration file, and configure the
+.Li reset
+GPIO
+number accordingly. Linuxspi can be used as follows:
+.Dl avrdude -c linuxspi -P /dev/spidev:/dev/gpiochip[:resetpin]
+.Pp
 Atmel's STK500 programmer is also supported and connects to a serial
 port.
 Both, firmware versions 1.x and 2.x can be handled, but require a
diff --git a/avrdude.conf.in b/avrdude.conf.in
index dd138cd9..57385dcc 100644
--- a/avrdude.conf.in
+++ b/avrdude.conf.in
@@ -1487,6 +1487,23 @@ programmer
 #  miso  = ?;
 #;
 
+
+# This programmer uses the built in linux SPI bus devices to program an
+# attached AVR. A GPIO accessed through the sysfs GPIO interface needs to
+# be specified for a reset pin since the linux SPI userspace functions do
+# not allow for control over the slave select/chip select signal.
+#
+# To use it, copy this snippet into your ~/.avrduderc, make sure the
+# 'reset' entry is configured correctly.
+#
+# programmer
+#   id = "linuxspi";
+#   desc = "Use Linux SPI device in /dev/spidev*";
+#   type = "linuxspi";
+#   reset = 25;
+#   baudrate=400000;
+# ;
+
 # some ultra cheap programmers use bitbanging on the 
 # serialport.
 #
diff --git a/configure.ac b/configure.ac
index 25a0e1e6..45b05e63 100644
--- a/configure.ac
+++ b/configure.ac
@@ -336,6 +336,18 @@ AC_ARG_ENABLE(
 		esac],
 	[enabled_linuxgpio=no])	
 
+AC_ARG_ENABLE(
+	[linuxspi],
+	AC_HELP_STRING(
+		[--enable-linuxspi],
+		[Enable the Linux SPIDEV interface programmer type]),
+	[case "${enableval}" in
+		yes) enabled_linuxspi=yes ;;
+		no)  enabled_linuxspi=no ;;
+		*)   AC_MSG_ERROR(bad value ${enableval} for enable-linuxspi option) ;;
+		esac],
+	[enabled_linuxspi=no])
+
 DIST_SUBDIRS_AC='doc windows'
 
 if test "$enabled_doc" = "yes"; then
@@ -413,6 +425,14 @@ else
 fi
 
 
+if test "$enabled_linuxspi" = "yes"; then
+	AC_DEFINE(HAVE_LINUXSPI, 1, [Linux SPI support enabled])
+	confsubst="$confsubst -e /^@HAVE_LINUXSPI_/d"
+else
+	confsubst="$confsubst -e /^@HAVE_LINUXSPI_BEGIN@/,/^@HAVE_LINUXSPI_END@/d"
+fi
+
+
 # If we are compiling with gcc, enable all warning and make warnings errors.
 if test "$GCC" = yes; then
     ENABLE_WARNINGS="-Wall"
@@ -590,3 +610,9 @@ else
    echo "DISABLED   linuxgpio"
 fi
 
+if test x$enabled_linuxspi = xyes; then
+   echo "ENABLED    linuxspi"
+else
+   echo "DISABLED   linuxspi"
+fi
+
diff --git a/doc/avrdude.texi b/doc/avrdude.texi
index de525a26..683d7c8f 100644
--- a/doc/avrdude.texi
+++ b/doc/avrdude.texi
@@ -180,6 +180,33 @@ some resistors in series or better yet use a 3-state buffer driver like
 the 74HC244. Have a look at http://kolev.info/avrdude-linuxgpio for a more
 detailed tutorial about using this programmer type.
 
+Under a Linux installation with direct access to the SPI bus and GPIO
+pins, such as would be found on a Raspberry Pi, the ``linuxspi''
+programmer type can be used to directly connect to and program a chip
+using the built in interfaces on the computer. The requirements to use
+this type are that an SPI interface is exposed along with one GPIO
+pin. The GPIO serves as the reset output since the Linux SPI drivers
+do not hold slave select down when a transfer is not occuring and thus
+it cannot be used as the reset pin. A readily available level
+translator should be used between the SPI bus/reset GPIO and the chip
+to avoid potentially damaging the computer's SPI controller in the
+event that the chip is running at 5V and the SPI runs at 3.3V. The
+GPIO chosen for reset can be configured in the avrdude configuration
+file using the @code{reset} entry under the linuxspi programmer, or
+directly in the port specification. An external pull-up resistor
+should be connected between the AVR's reset pin and Vcc. If Vcc is not
+the same as the SPI voltage, this should be done on the AVR side of
+the level translator to protect the hardware from damage.
+
+A commented-out template for this programmer is provided in the
+avrdude configuration file.  To use it, clone that entry into the
+per-user configuration file, and configure the @code{reset} GPIO
+number accordingly. Linuxspi can be used as follows:
+
+@smallexample
+avrdude -c linuxspi -P /dev/spidev:/dev/gpiochip[:resetpin]
+@end smallexample
+
 The STK500, JTAG ICE, avr910, and avr109/butterfly use the serial port to communicate with the PC.
 The STK600, JTAG ICE mkII/3, AVRISP mkII, USBasp, avrftdi (and derivatives), and USBtinyISP
 programmers communicate through the USB, using @code{libusb} as a
diff --git a/linuxspi.c b/linuxspi.c
new file mode 100644
index 00000000..416e89ef
--- /dev/null
+++ b/linuxspi.c
@@ -0,0 +1,302 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Support for using spidev userspace drivers to communicate directly over SPI
+ *
+ * Copyright (C) 2013 Kevin Cuzner <kevin@kevincuzner.com>
+ * Copyright (C) 2018 Ralf Ramsauer <ralf@vmexit.de>
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Support for inversion of reset pin, Tim Chilton 02/05/2014
+ * Review code, rebase to latest trunk, add linux/gpio.h support, Ralf Ramsauer 2018-09-07
+ */
+
+
+#include "ac_cfg.h"
+
+#include "avrdude.h"
+#include "libavrdude.h"
+
+#include "linuxspi.h"
+
+#if HAVE_LINUXSPI
+
+/**
+ * Linux Kernel SPI Drivers
+ *
+ * Copyright (C) 2006 SWAPP
+ *      Andrea Paterniani <a.paterniani@swapp-eng.it>
+ * Copyright (C) 2007 David Brownell (simplification, cleanup)
+ *
+ * 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.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <linux/types.h>
+#include <linux/spi/spidev.h>
+#include <linux/gpio.h>
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#define LINUXSPI "linuxspi"
+
+static int fd_spidev, fd_gpiochip, fd_linehandle;
+
+/**
+ * @brief Sends/receives a message in full duplex mode
+ * @return -1 on failure, otherwise number of bytes sent/recieved
+ */
+static int linuxspi_spi_duplex(PROGRAMMER *pgm, const unsigned char *tx, unsigned char *rx, int len)
+{
+    struct spi_ioc_transfer tr;
+    int ret;
+
+    tr = (struct spi_ioc_transfer) {
+        .tx_buf = (unsigned long)tx,
+        .rx_buf = (unsigned long)rx,
+        .len = len,
+        .delay_usecs = 1,
+        //should settle around 400Khz, a standard SPI speed. Adjust using baud parameter (-b)
+	.speed_hz = pgm->baudrate == 0 ? 400000 : pgm->baudrate,
+        .bits_per_word = 8,
+    };
+
+    ret = ioctl(fd_spidev, SPI_IOC_MESSAGE(1), &tr);
+    if (ret != len)
+        avrdude_message(MSG_INFO, "\n%s: error: Unable to send SPI message\n", progname);
+
+    return (ret == -1) ? -1 : 0;
+}
+
+static void linuxspi_setup(PROGRAMMER *pgm)
+{
+}
+
+static void linuxspi_teardown(PROGRAMMER* pgm)
+{
+}
+
+static int linuxspi_open(PROGRAMMER *pgm, char *port)
+{
+    const char *port_error = "%s: error: Unknown port specification. Please use the format /dev/spidev:/dev/gpiochip[:resetno]\n";
+    char *spidev, *gpiochip, *reset_pin;
+    struct gpiohandle_request req;
+    struct gpiohandle_data data;
+    int ret;
+
+    if (!port || !strcmp(port, "unknown")) {
+        avrdude_message(MSG_INFO, "%s: error: No port specified. Port should point to an spidev device.\n", progname);
+        return -1;
+    }
+
+    spidev = strtok(port, ":");
+    if (!spidev) {
+        avrdude_message(MSG_INFO, port_error, progname);
+        return -1;
+    }
+
+    gpiochip = strtok(NULL, ":");
+    if (!gpiochip) {
+        avrdude_message(MSG_INFO, port_error, progname);
+        return -1;
+    }
+
+    /* optional: override reset pin in configuration */
+    reset_pin = strtok(NULL, ":");
+    if (reset_pin)
+        pgm->pinno[PIN_AVR_RESET] = strtoul(reset_pin, NULL, 0);
+
+    strcpy(pgm->port, port);
+    fd_spidev = open(pgm->port, O_RDWR);
+    if (fd_spidev < 0) {
+        avrdude_message(MSG_INFO, "\n%s: error: Unable to open the spidev device %s", progname, pgm->port);
+        return -1;
+    }
+
+    fd_gpiochip = open(gpiochip, 0);
+    if (fd_gpiochip < 0) {
+        close(fd_spidev);
+        avrdude_message(MSG_INFO, "\n%s error: Unable to open the gpiochip %s", progname, gpiochip);
+        ret = -1;
+        goto close_spidev;
+    }
+
+    strcpy(req.consumer_label, progname);
+    req.lines = 1;
+    req.lineoffsets[0] = pgm->pinno[PIN_AVR_RESET];
+    req.flags = GPIOHANDLE_REQUEST_OUTPUT;
+
+    ret = ioctl(fd_gpiochip, GPIO_GET_LINEHANDLE_IOCTL, &req);
+    if (ret == -1) {
+        ret = -errno;
+        goto close_gpiochip;
+    }
+
+    fd_linehandle = req.fd;
+
+    /*
+     * Set the reset state and keep it. The pin will be released and set back to
+     * its initial value, once the fd_gpiochip is closed.
+     */
+    data.values[0] = !!(pgm->pinno[PIN_AVR_RESET] & PIN_INVERSE);
+    ret = ioctl(fd_linehandle, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
+    if (ret == -1) {
+        ret = -errno;
+        goto close_out;
+    }
+
+    return 0;
+
+close_out:
+    close(fd_linehandle);
+close_gpiochip:
+    close(fd_gpiochip);
+close_spidev:
+    close(fd_spidev);
+    return ret;
+}
+
+static void linuxspi_close(PROGRAMMER *pgm)
+{
+    close(fd_spidev);
+    close(fd_gpiochip);
+}
+
+static void linuxspi_disable(PROGRAMMER* pgm)
+{
+}
+
+static void linuxspi_enable(PROGRAMMER* pgm)
+{
+}
+
+static void linuxspi_display(PROGRAMMER* pgm, const char* p)
+{
+}
+
+static int linuxspi_initialize(PROGRAMMER *pgm, AVRPART *p)
+{
+    int tries, ret;
+
+    if (p->flags & AVRPART_HAS_TPI) {
+        /* We do not support tpi. This is a dedicated SPI thing */
+        avrdude_message(MSG_INFO, "%s: error: Programmer " LINUXSPI " does not support TPI\n", progname);
+        return -1;
+    }
+
+    //enable programming on the part
+    tries = 0;
+    do
+    {
+        ret = pgm->program_enable(pgm, p);
+        if (ret == 0 || ret == -1)
+            break;
+    } while(tries++ < 65);
+
+    if (ret)
+        avrdude_message(MSG_INFO, "%s: error: AVR device not responding\n", progname);
+
+    return ret;
+}
+
+static int linuxspi_cmd(PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res)
+{
+    return linuxspi_spi_duplex(pgm, cmd, res, 4);
+}
+
+static int linuxspi_program_enable(PROGRAMMER *pgm, AVRPART *p)
+{
+    unsigned char cmd[4], res[4];
+
+    if (!p->op[AVR_OP_PGM_ENABLE]) {
+        avrdude_message(MSG_INFO, "%s: error: program enable instruction not defined for part \"%s\"\n", progname, p->desc);
+        return -1;
+    }
+
+    memset(cmd, 0, sizeof(cmd));
+    avr_set_bits(p->op[AVR_OP_PGM_ENABLE], cmd); //set the cmd
+    pgm->cmd(pgm, cmd, res);
+
+    if (res[2] != cmd[1])
+        return -2;
+
+    return 0;
+}
+
+static int linuxspi_chip_erase(PROGRAMMER *pgm, AVRPART *p)
+{
+    unsigned char cmd[4], res[4];
+
+    if (!p->op[AVR_OP_CHIP_ERASE]) {
+        avrdude_message(MSG_INFO, "%s: error: chip erase instruction not defined for part \"%s\"\n", progname, p->desc);
+        return -1;
+    }
+
+    memset(cmd, 0, sizeof(cmd));
+    avr_set_bits(p->op[AVR_OP_CHIP_ERASE], cmd);
+    pgm->cmd(pgm, cmd, res);
+    usleep(p->chip_erase_delay);
+    pgm->initialize(pgm, p);
+
+    return 0;
+}
+
+void linuxspi_initpgm(PROGRAMMER *pgm)
+{
+    strcpy(pgm->type, LINUXSPI);
+
+    pgm_fill_old_pins(pgm); // TODO to be removed if old pin data no longer needed
+
+    /* mandatory functions */
+    pgm->initialize     = linuxspi_initialize;
+    pgm->display        = linuxspi_display;
+    pgm->enable         = linuxspi_enable;
+    pgm->disable        = linuxspi_disable;
+    pgm->program_enable = linuxspi_program_enable;
+    pgm->chip_erase     = linuxspi_chip_erase;
+    pgm->cmd            = linuxspi_cmd;
+    pgm->open           = linuxspi_open;
+    pgm->close          = linuxspi_close;
+    pgm->read_byte      = avr_read_byte_default;
+    pgm->write_byte     = avr_write_byte_default;
+
+    /* optional functions */
+    pgm->setup          = linuxspi_setup;
+    pgm->teardown       = linuxspi_teardown;
+}
+
+const char linuxspi_desc[] = "SPI using Linux spidev driver";
+
+#else /* !HAVE_LINUXSPI */
+
+void linuxspi_initpgm(PROGRAMMER * pgm)
+{
+    avrdude_message(MSG_INFO, "%s: Linux SPI driver not available in this configuration\n",
+                    progname);
+}
+
+const char linuxspi_desc[] = "SPI using Linux spidev driver (not available)";
+
+#endif /* HAVE_LINUXSPI */
diff --git a/linuxspi.h b/linuxspi.h
new file mode 100644
index 00000000..06c6dd20
--- /dev/null
+++ b/linuxspi.h
@@ -0,0 +1,35 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2013 Kevin Cuzner <kevin@kevincuner.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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef linuxspi_h
+#define linuxspi_h
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const char linuxspi_desc[];
+void linuxspi_initpgm        (PROGRAMMER * pgm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //linuxspi_h
+
diff --git a/pgm_type.c b/pgm_type.c
index 9b6bdccc..4297f4b9 100644
--- a/pgm_type.c
+++ b/pgm_type.c
@@ -40,6 +40,7 @@
 #include "jtagmkII.h"
 #include "jtag3.h"
 #include "linuxgpio.h"
+#include "linuxspi.h"
 #include "par.h"
 #include "pickit2.h"
 #include "ppi.h"
@@ -81,6 +82,7 @@ const PROGRAMMER_TYPE programmers_types[] = {
         {"jtagice3_dw", jtag3_dw_initpgm, jtag3_dw_desc},
         {"jtagice3_isp", stk500v2_jtag3_initpgm, stk500v2_jtag3_desc},
         {"linuxgpio", linuxgpio_initpgm, linuxgpio_desc},
+        {"linuxspi", linuxspi_initpgm, linuxspi_desc},
         {"par", par_initpgm, par_desc},
         {"pickit2", pickit2_initpgm, pickit2_desc},
         {"serbb", serbb_initpgm, serbb_desc},