From df0268ccc1e760be98e4bdf5ecf1506a1dca0075 Mon Sep 17 00:00:00 2001
From: Kevin Cuzner <kevin@kevincuzner.com>
Date: Thu, 31 Oct 2013 10:08:24 -0600
Subject: [PATCH] Added linuxspi programmer type using spidev

The spidev userspace drivers can be used to directly program an AVR
attached to the internal SPI bus of the machine that this is running
on. Works only on linux.
---
 avrdude/Makefile.am      |   2 +
 avrdude/avrdude.conf.in  |  14 ++
 avrdude/configure.ac     |  13 +-
 avrdude/doc/avrdude.texi |  20 +++
 avrdude/linuxspi.c       | 393 +++++++++++++++++++++++++++++++++++++++++++++++
 avrdude/linuxspi.h       |  37 +++++
 avrdude/pgm_type.c       |   2 +
 7 files changed, 480 insertions(+), 1 deletion(-)
 create mode 100644 avrdude/linuxspi.c
 create mode 100644 avrdude/linuxspi.h

diff --git a/avrdude/Makefile.am b/avrdude/Makefile.am
index 885636e..f40a1de 100644
--- a/avrdude/Makefile.am
+++ b/avrdude/Makefile.am
@@ -129,6 +129,8 @@ libavrdude_a_SOURCES = \
 	jtag3_private.h \
 	linuxgpio.c \
 	linuxgpio.h \
+	linuxspi.c \
+	linuxspi.h \
 	linux_ppdev.h \
 	lists.c \
 	lists.h \
diff --git a/avrdude/avrdude.conf.in b/avrdude/avrdude.conf.in
index f66be2a..c0cc4e3 100644
--- a/avrdude/avrdude.conf.in
+++ b/avrdude/avrdude.conf.in
@@ -1261,6 +1261,20 @@ 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.
+#
+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/avrdude/configure.ac b/avrdude/configure.ac
index 2e8f62a..236e3df 100644
--- a/avrdude/configure.ac
+++ b/avrdude/configure.ac
@@ -186,7 +186,12 @@ AC_CHECK_HEADERS([limits.h stdlib.h string.h])
 AC_CHECK_HEADERS([fcntl.h sys/ioctl.h sys/time.h termios.h unistd.h])
 AC_CHECK_HEADERS([ddk/hidsdi.h],,,[#include <windows.h>
 #include <setupapi.h>])
-
+AH_TEMPLATE([HAVE_SPIDEV],
+            [Define if spidev support is enabled for linux])
+AC_CHECK_HEADERS([linux/spi/spidev.h], [have_spidev=yes])
+if test x$have_spidev == xyes; then
+    AC_DEFINE([HAVE_SPIDEV])
+fi
 
 # Checks for typedefs, structures, and compiler characteristics.
 AC_C_CONST
@@ -544,3 +549,9 @@ else
    echo "DISABLED   linuxgpio"
 fi
 
+if test x$have_spidev = xyes; then
+   echo "ENABLED    linuxspi"
+else
+   echo "DISABLED   linnuxspi"
+fi
+
diff --git a/avrdude/doc/avrdude.texi b/avrdude/doc/avrdude.texi
index 6f3331a..6e30f20 100644
--- a/avrdude/doc/avrdude.texi
+++ b/avrdude/doc/avrdude.texi
@@ -180,6 +180,26 @@ 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 'reset' entry under the
+linuxspi programmer. 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. By default, this programmer is commented
+out in the avrdude configuration file due to the lack of a reliable way
+to determine if GPIOs are available. To use it, uncomment the applicable
+lines and set the 'reset' GPIO number accordingly.
+
 The STK500, JTAG ICE, avr910, and avr109/butterfly use the serial port to communicate with the PC.
 The STK600, JTAG ICE mkII, AVRISP mkII, USBasp, avrftdi (and derivitives), and USBtinyISP
 programmers communicate through the USB, using @code{libusb} as a
diff --git a/avrdude/linuxspi.c b/avrdude/linuxspi.c
new file mode 100644
index 0000000..25272d8
--- /dev/null
+++ b/avrdude/linuxspi.c
@@ -0,0 +1,393 @@
+/*
+ * 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>
+ *
+ * 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
+ */
+ 
+#include "linuxspi.h"
+
+#include "ac_cfg.h"
+
+#include "avrdude.h"
+#include "avr.h"
+#include "pindefs.h"
+
+#if HAVE_SPIDEV
+
+/**
+ * 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 <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <linux/types.h>
+#include <linux/spi/spidev.h>
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+/**
+ * Data for the programmer
+ */
+
+struct pdata
+{
+    unsigned int speedHz;
+};
+
+typedef enum {
+    LINUXSPI_GPIO_DIRECTION,
+    LINUXSPI_GPIO_VALUE,
+    LINUXSPI_GPIO_EXPORT,
+    LINUXSPI_GPIO_UNEXPORT
+} LINUXSPI_GPIO_OP;
+
+#define PDATA(pgm) ((struct pdata *)(pgm->cookie))
+#define IMPORT_PDATA(pgm) struct pdata *pdata = PDATA(pgm)
+
+/**
+ * Function Prototypes
+ */
+
+//linuxspi specific functions
+static int linuxspi_spi_duplex(PROGRAMMER* pgm, unsigned char* tx, unsigned char* rx, int len);
+static int linuxspi_gpio_op_wr(PROGRAMMER* pgm, LINUXSPI_GPIO_OP op, int gpio, char* val);
+//interface - management
+static void linuxspi_setup(PROGRAMMER* pgm);
+static void linuxspi_teardown(PROGRAMMER* pgm);
+//interface - prog
+static int linuxspi_open(PROGRAMMER* pgm, char* port);
+static void linuxspi_close(PROGRAMMER* pgm);
+// dummy functions
+static void linuxspi_disable(PROGRAMMER * pgm);
+static void linuxspi_enable(PROGRAMMER * pgm);
+static void linuxspi_display(PROGRAMMER * pgm, const char * p);
+//universal
+static int linuxspi_initialize(PROGRAMMER* pgm, AVRPART* p);
+// SPI specific functions
+static int linuxspi_cmd(PROGRAMMER * pgm, unsigned char cmd[4], unsigned char res[4]);
+static int linuxspi_program_enable(PROGRAMMER * pgm, AVRPART * p);
+static int linuxspi_chip_erase(PROGRAMMER * pgm, AVRPART * p);
+
+/**
+ * @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, unsigned char* tx, unsigned char* rx, int len)
+{
+    int fd = open(pgm->port, O_RDWR);
+    if (fd < 0)
+    {
+        fprintf(stderr, "\n%s: error: Unable to open SPI port %s", progname, pgm->port);
+        return -1; //error
+    }
+    
+    struct spi_ioc_transfer tr = {
+        .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,
+    };
+    
+    int ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
+    close(fd);
+    
+    if (ret != len)
+    {
+        fprintf(stderr, "\n%s: error: Unable to send SPI message\n", progname);
+        return -1;
+    }
+    
+    return ret;
+}
+
+/**
+ * @brief Performs an operation on a gpio. Writes to stderr if error.
+ * @param op Operation to perform
+ * @param gpio 
+ * @return -1 if failed, 0 otherwise
+ */
+static int linuxspi_gpio_op_wr(PROGRAMMER* pgm, LINUXSPI_GPIO_OP op, int gpio, char* val)
+{
+    char* fn = malloc(PATH_MAX); //filename
+    
+    switch(op)
+    {
+        case LINUXSPI_GPIO_DIRECTION:
+            sprintf(fn, "/sys/class/gpio/gpio%d/direction", gpio);
+            break;
+        case LINUXSPI_GPIO_EXPORT:
+            sprintf(fn, "/sys/class/gpio/export");
+            break;
+        case LINUXSPI_GPIO_UNEXPORT:
+            sprintf(fn, "/sys/class/gpio/unexport");
+            break;
+        case LINUXSPI_GPIO_VALUE:
+            sprintf(fn, "/sys/class/gpio/gpio%d/value", gpio);
+            break;
+        default:
+            fprintf(stderr, "%s: linuxspi_gpio_op_wr(): Unknown op %d", progname, op);
+            return -1;
+    }
+    
+    FILE* f = fopen(fn, "w");
+    
+    if (!f)
+    {
+        fprintf(stderr, "%s: linuxspi_gpio_op_wr(): Unable to open file %s", progname, fn);
+        free(fn); //we no longer need the path
+        return -1;
+    }
+    
+    if (fprintf(f, val) < 0)
+    {
+        fprintf(stderr, "%s: linuxspi_gpio_op_wr(): Unable to write file %s with %s", progname, fn, val);
+        free(fn); //we no longer need the path
+        return -1;
+    }
+    
+    fclose(f);
+    free(fn); //we no longer need the path
+    
+    return 0;
+}
+
+static void linuxspi_setup(PROGRAMMER* pgm)
+{
+    if ((pgm->cookie = malloc(sizeof(struct pdata))) == 0)
+    {
+        fprintf(stderr, "%s: linuxspi_setup(): Unable to allocate private memory.\n", progname);
+        exit(1);
+    }
+    memset(pgm->cookie, 0, sizeof(struct pdata));
+}
+
+static void linuxspi_teardown(PROGRAMMER* pgm)
+{
+    free(pgm->cookie);
+}
+
+static int linuxspi_open(PROGRAMMER* pgm, char* port)
+{   
+    char* buf;
+    
+    if (port == 0 || strcmp(port, "unknown") == 0) //unknown port
+    {
+        fprintf(stderr, "%s: error: No port specified. Port should point to an SPI interface.\n", progname);
+        exit(1);
+    }
+    
+    if (pgm->pinno[PIN_AVR_RESET] == 0)
+    {
+        fprintf(stderr, "%s: error: No pin assigned to AVR RESET.\n", progname);
+        exit(1);
+    }
+    
+    //export reset pin
+    buf = malloc(32);
+    sprintf(buf, "%d", pgm->pinno[PIN_AVR_RESET]);
+    if (linuxspi_gpio_op_wr(pgm, LINUXSPI_GPIO_EXPORT, pgm->pinno[PIN_AVR_RESET], buf) < 0)
+    {
+        free(buf);
+        return -1;
+    }
+    free(buf);
+    
+    //set reset to output
+    if (linuxspi_gpio_op_wr(pgm, LINUXSPI_GPIO_DIRECTION, pgm->pinno[PIN_AVR_RESET], "out") < 0)
+    {
+        return -1;
+    }
+    
+    //set reset low
+    if (linuxspi_gpio_op_wr(pgm, LINUXSPI_GPIO_VALUE, pgm->pinno[PIN_AVR_RESET], "0") < 0)
+    {
+        return -1;
+    }
+        
+    //save the port to our data
+    strcpy(pgm->port, port);
+    
+    return 0;
+}
+
+static void linuxspi_close(PROGRAMMER* pgm)
+{
+    char* buf;
+    
+    //set reset to input
+    linuxspi_gpio_op_wr(pgm, LINUXSPI_GPIO_DIRECTION, pgm->pinno[PIN_AVR_RESET], "in");
+    
+    //unexport reset
+    buf = malloc(32);
+    sprintf(buf, "%d", pgm->pinno[PIN_AVR_RESET]);
+    linuxspi_gpio_op_wr(pgm, LINUXSPI_GPIO_UNEXPORT, pgm->pinno[PIN_AVR_RESET], buf);
+}
+
+static void linuxspi_disable(PROGRAMMER* pgm)
+{
+    //do nothing
+}
+
+static void linuxspi_enable(PROGRAMMER* pgm)
+{
+    //do nothing
+}
+
+static void linuxspi_display(PROGRAMMER* pgm, const char* p)
+{
+    //do nothing
+}
+
+static int linuxspi_initialize(PROGRAMMER* pgm, AVRPART* p)
+{
+    int tries, rc;
+    
+    if (p->flags & AVRPART_HAS_TPI)
+    {
+        //we do not support tpi..this is a dedicated SPI thing
+        fprintf(stderr, "%s: error: Programmer %s does not support TPI\n", progname, pgm->type);
+        return -1;
+    }
+    
+    //enable programming on the part
+    tries = 0;
+    do
+    {
+        rc = pgm->program_enable(pgm, p);
+        if (rc == 0 || rc == -1)
+            break;
+        tries++;
+    }
+    while(tries < 65);
+    
+    if (rc)
+    {
+        fprintf(stderr, "%s: error: AVR device not responding\n", progname);
+        return -1;
+    }
+    
+    return 0;
+}
+
+static int linuxspi_cmd(PROGRAMMER* pgm, unsigned char cmd[4], unsigned char res[4])
+{
+    return linuxspi_spi_duplex(pgm, cmd, res, 4);
+}
+
+static int linuxspi_program_enable(PROGRAMMER* pgm, AVRPART* p)
+{
+    unsigned char cmd[4];
+    unsigned char res[4];
+    
+    if (p->op[AVR_OP_PGM_ENABLE] == NULL)
+    {
+        fprintf(stderr, "%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];
+    unsigned char res[4];
+    
+    if (p->op[AVR_OP_CHIP_ERASE] == NULL)
+    {
+        fprintf(stderr, "%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
+
+void linuxspi_initpgm(PROGRAMMER * pgm)
+{
+    fprintf(stderr,
+      "%s: Linux SPI driver not available in this configuration\n",
+      progname);
+}
+
+const char linuxspi_desc[] = "SPI using Linux spidev driver (not available)";
+
+#endif
diff --git a/avrdude/linuxspi.h b/avrdude/linuxspi.h
new file mode 100644
index 0000000..536a6f1
--- /dev/null
+++ b/avrdude/linuxspi.h
@@ -0,0 +1,37 @@
+/*
+ * 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
+ */
+
+#include "pgm.h"
+
+#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/avrdude/pgm_type.c b/avrdude/pgm_type.c
index 7f36dfd..2b25486 100644
--- a/avrdude/pgm_type.c
+++ b/avrdude/pgm_type.c
@@ -39,6 +39,7 @@
 #include "jtagmkII.h"
 #include "jtag3.h"
 #include "linuxgpio.h"
+#include "linuxspi.h"
 #include "par.h"
 #include "pickit2.h"
 #include "ppi.h"
@@ -77,6 +78,7 @@ const PROGRAMMER_TYPE const 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},
-- 
1.8.4.1

