Hi,

This is my first time ever posting on a mailing list like this before,
but I have added to my own copy of avrdude support for the spidev
drivers that are available in the Linux userspace since around kernel
2.6 or so. I want to submit my patch back to see what is thought of it
and I read somewhere that this is the sort of place to post these sort
of things. The patch begins at trunk revision 1183.

The main point of this addition is to allow single board computers which
run Linux and expose an SPI interface (such as a Raspberry Pi) to talk
directly to an AVR and program it over SPI with no additional hardware
other than a resistor from Reset to Vcc.

I've never modified an open source program before, but I have attached
my changes formatted as a single patch. I was using git, so I hope that
doesn't make things difficult.

I have the full, unsquashed changes on my github repo here:
https://github.com/kcuzner/avrdude

A blog post detailing the hardware side of the process can be found
here: http://kevincuzner.com/2013/05/27/raspberry-pi-as-an-avr-programmer/

I apologize if my attachment is too large or if I have posted in the
wrong place. Again, I have never done this before, so bear with my
mailing list etiquette shortcomings.

Thanks,
 - Kevin Cuzner
>From c7dfefea9b35426a61ebfca823fe853654b0b563 Mon Sep 17 00:00:00 2001
From: Kevin Cuzner <[email protected]>
Date: Wed, 5 Jun 2013 00:03:24 -0600
Subject: [PATCH] Squash commit of all changes for spidev programmer type

Add spidev programmer type
Add ./configure detection for linux/spi/spidev.h
Add short documentation for spidev programmer type
Add "default" avrdude.conf entry for spidev programmer type
---
 avrdude/.gitignore       |  40 +++++
 avrdude/Makefile.am      |   2 +
 avrdude/avrdude.conf.in  |  13 ++
 avrdude/configure.ac     |  13 +-
 avrdude/doc/avrdude.texi |  17 ++
 avrdude/linuxspi.c       | 392 +++++++++++++++++++++++++++++++++++++++++++++++
 avrdude/linuxspi.h       |  37 +++++
 avrdude/pgm_type.c       |   2 +
 8 files changed, 515 insertions(+), 1 deletion(-)
 create mode 100644 avrdude/.gitignore
 create mode 100644 avrdude/linuxspi.c
 create mode 100644 avrdude/linuxspi.h

diff --git a/avrdude/.gitignore b/avrdude/.gitignore
new file mode 100644
index 0000000..6c0de93
--- /dev/null
+++ b/avrdude/.gitignore
@@ -0,0 +1,40 @@
+*.diff
+*.patch
+y.output
+y.tab.h
+lexer.c
+config_gram.c
+config_gram.h
+.cvsignore
+.depend
+.deps
+INSTALL
+Makefile.in
+Makefile
+ac_cfg.h.in
+ac_cfg.h.in~
+aclocal.m4
+autom4te.cache
+configure
+depcomp
+install-sh
+compile
+missing
+mkinstalldirs
+stamp-h.in
+stamp-h1
+ac_cfg.h
+avrdude.conf
+avrdude.conf.tmp
+avrdude.spec
+config.guess
+config.log
+config.status
+config.sub
+avrdude
+
+*.o
+*.a
+doc/mdate-sh
+doc/texinfo.tex
+ylwrap
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 2cc5d7b..bc0b086 100644
--- a/avrdude/avrdude.conf.in
+++ b/avrdude/avrdude.conf.in
@@ -1200,6 +1200,19 @@ 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;
+;
+
 # some ultra cheap programmers use bitbanging on the 
 # serialport.
 #
diff --git a/avrdude/configure.ac b/avrdude/configure.ac
index 4bd32b1..a2c7863 100644
--- a/avrdude/configure.ac
+++ b/avrdude/configure.ac
@@ -179,7 +179,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
@@ -537,3 +542,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 c2b1e47..2be71a6 100644
--- a/avrdude/doc/avrdude.texi
+++ b/avrdude/doc/avrdude.texi
@@ -180,6 +180,23 @@ 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. 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 this, 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..2789bc4
--- /dev/null
+++ b/avrdude/linuxspi.c
@@ -0,0 +1,392 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Support for using spidev userspace drivers to communicate directly over SPI
+ * 
+ * Copyright (C) 2013 Kevin Cuzner <[email protected]>
+ *
+ * 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 <[email protected]>
+ * 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,
+        .speed_hz = 500000, //should settle around 400Khz, a standard SPI speed
+        .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 <[email protected]>
+ *
+ * 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.3

_______________________________________________
avrdude-dev mailing list
[email protected]
https://lists.nongnu.org/mailman/listinfo/avrdude-dev

Reply via email to