From 40b0b104d6967ca75929706996b3dede304b9b25 Mon Sep 17 00:00:00 2001 From: Joerg Wunsch 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 + + 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 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 + * Copyright (C) 2018 Ralf Ramsauer + * + * 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 + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 + * + * 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},