avrdude-dev
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[avrdude-dev] [PATCH 1/1] Implement new programmer type: linuxspi


From: Ralf Ramsauer
Subject: [avrdude-dev] [PATCH 1/1] Implement new programmer type: linuxspi
Date: Mon, 24 Sep 2018 22:49:32 +0200

From: Kevin Cuzner <address@hidden>

This adds a new linuxspi programmer type. Analogously to linuxgpio, this
programmer uses the Linux-specific spidev device.

For accessing the reset pin, this programmer uses an additional GPIO pin using
the linux/gpio.h API. In contrast to the stateless and deprecated sysfs
interface, linux/gpio.h uses proper file descriptors for every pin, and
performs housekeeping when the device is closed. That saves some lines of code.

The programmer can be used as follows:
  avrdude -c linuxspi -P /dev/spidev:/dev/gpiochip[:resetpin] [...]

Separated by colons, the first part of the port (-P) argument denotes the
spidev device, the second one is the gpiochip that contains the reset pin, and
the optional third argument contains the pin number of the reset pin.

If not specified, the pin number defaults to the reset pin number defined in
avrdude.conf. Currently, this defaults to 25, which is connected to Pin 22 on a
Raspberry Pi.

Additionally, add some documentation.

Credits primarily go to Kevin Cuzner, who started this fork ~5 years ago. I
picked it up, rebased it to the current mainline, massaged some parts of the
code here and there, fixed some minor bugs and switched to the linux/gpio.h
GPIO accessors. Pin inversion support comes from Tim Chilton, which I ported to
the linux/gpio.h API.

Signed-off-by: Ralf Ramsauer <address@hidden>
---
 avrdude/Makefile.am      |   2 +
 avrdude/avrdude.conf.in  |  14 ++
 avrdude/configure.ac     |  27 +++-
 avrdude/doc/avrdude.texi |  20 +++
 avrdude/linuxspi.c       | 302 +++++++++++++++++++++++++++++++++++++++
 avrdude/linuxspi.h       |  35 +++++
 avrdude/pgm_type.c       |   2 +
 7 files changed, 401 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 4966866..eff0993 100644
--- a/avrdude/Makefile.am
+++ b/avrdude/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/avrdude/avrdude.conf.in b/avrdude/avrdude.conf.in
index 858fe0f..f1d2293 100644
--- a/avrdude/avrdude.conf.in
+++ b/avrdude/avrdude.conf.in
@@ -1479,6 +1479,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 2dd7b4b..1b1a3e9 100644
--- a/avrdude/configure.ac
+++ b/avrdude/configure.ac
@@ -219,7 +219,6 @@ 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>])
 
-
 # Checks for typedefs, structures, and compiler characteristics.
 AC_C_CONST
 AC_HEADER_TIME
@@ -336,6 +335,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 +424,14 @@ else
 fi
 
 
+if test "$enabled_linuxspi" = "yes"; then
+       AC_DEFINE(HAVE_LINUXSPI, 1, [Linux SPI support enabled])
+       confsubst="$confsubst -e /address@hidden/d"
+else
+       confsubst="$confsubst -e /address@hidden@/,/address@hidden@/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 +609,9 @@ else
    echo "DISABLED   linuxgpio"
 fi
 
+if test x$enabled_linuxspi = xyes; then
+   echo "ENABLED    linuxspi"
+else
+   echo "DISABLED   linuxspi"
+fi
+
diff --git a/avrdude/doc/avrdude.texi b/avrdude/doc/avrdude.texi
index 076ca6d..61cd333 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, 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. 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. Linuxspi can be used as 
follows:
+  avrdude -c linuxspi -P /dev/spidev:/dev/gpiochip[:resetpin]
+
 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/avrdude/linuxspi.c b/avrdude/linuxspi.c
new file mode 100644
index 0000000..416e89e
--- /dev/null
+++ b/avrdude/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 <address@hidden>
+ * Copyright (C) 2018 Ralf Ramsauer <address@hidden>
+ *
+ * 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 <address@hidden>
+ * 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/avrdude/linuxspi.h b/avrdude/linuxspi.h
new file mode 100644
index 0000000..06c6dd2
--- /dev/null
+++ b/avrdude/linuxspi.h
@@ -0,0 +1,35 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2013 Kevin Cuzner <address@hidden>
+ *
+ * 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/avrdude/pgm_type.c b/avrdude/pgm_type.c
index 9b6bdcc..4297f4b 100644
--- a/avrdude/pgm_type.c
+++ b/avrdude/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},
-- 
2.19.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]