Files
avrdude/src/linuxgpio.c
Stefan Rueger 38007133ef Rename type component of programmer to ptyp
The type component of the programmer structure can be, and on a number of
occasions has been, confused with the type component of the avrdude.conf
file. The latter is solely used to set the initpgm component. The former
is an internally used structure component that the user does not see.

Hence it is justified, and desirable, to rename the type component of the
programmer to avoid future confusion.
2026-06-28 15:18:44 +01:00

786 lines
21 KiB
C

/*
* avrdude - A Downloader/Uploader for AVR device programmers
* Support for bitbanging GPIO pins using libgpiod or the /sys/class/gpio interface
*
* Copyright (C) 2013 Radoslav Kolev <radoslav@kolev.info>
* Copyright (C) 2023 Sebastian Kuzminsky <seb@highlab.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 <ac_cfg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#ifdef HAVE_LIBGPIOD
#include <gpiod.h>
#endif
#include "avrdude.h"
#include "libavrdude.h"
#include "bitbang.h"
#if HAVE_LINUXGPIO
/*
* Sysfs GPIO user space helpers
*
* Copyright 2009 Analog Devices Inc.
* Michael Hennerich (hennerich@blackfin.uclinux.org)
*
* Licensed under the GPL-2 or later
*/
#define N_GPIO (PIN_MAX + 1)
struct pdata {
int sysfs_fds[N_GPIO]; // Open FDs of /sys/class/gpio/gpioXX/value for needed pins
};
// Use private programmer data as if they were a global structure my
#define my (*(struct pdata *)(pgm->cookie))
/*
* Sysfs GPIO user space helpers
* The following functions are acting on an "unsigned gpio" argument, which
* corresponds to the gpio numbering scheme in the kernel (starting from 0).
*/
#define GPIO_DIR_IN 0
#define GPIO_DIR_OUT 1
static int linuxgpio_sysfs_export(unsigned int gpio) {
int fd, len, r;
char buf[11];
fd = open("/sys/class/gpio/export", O_WRONLY);
if(fd < 0) {
pmsg_ext_error("cannot open /sys/class/gpio/export: %s\n", strerror(errno));
return fd;
}
len = snprintf(buf, sizeof(buf), "%u", gpio);
r = write(fd, buf, len);
close(fd);
return r;
}
static int linuxgpio_sysfs_unexport(unsigned int gpio) {
int fd, len, r;
char buf[11];
fd = open("/sys/class/gpio/unexport", O_WRONLY);
if(fd < 0) {
pmsg_ext_error("cannot open /sys/class/gpio/unexport: %s\n", strerror(errno));
return fd;
}
len = snprintf(buf, sizeof(buf), "%u", gpio);
r = write(fd, buf, len);
close(fd);
return r;
}
static int linuxgpio_sysfs_openfd(unsigned int gpio) {
char filepath[60];
snprintf(filepath, sizeof(filepath), "/sys/class/gpio/gpio%u/value", gpio);
return open(filepath, O_RDWR);
}
static int linuxgpio_sysfs_dir(unsigned int gpio, unsigned int dir) {
int fd, r;
char buf[60];
snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%u/direction", gpio);
fd = open(buf, O_WRONLY);
if(fd < 0) {
pmsg_ext_error("cannot open %s: %s\n", buf, strerror(errno));
return fd;
}
if(dir == GPIO_DIR_OUT)
r = write(fd, "out", 4);
else
r = write(fd, "in", 3);
close(fd);
return r;
}
static int linuxgpio_sysfs_dir_out(unsigned int gpio) {
return linuxgpio_sysfs_dir(gpio, GPIO_DIR_OUT);
}
static int linuxgpio_sysfs_dir_in(unsigned int gpio) {
return linuxgpio_sysfs_dir(gpio, GPIO_DIR_IN);
}
// End of Sysfs GPIO user space helpers
// Delay between checks for successful GPIO export (100ms)
#define GPIO_SYSFS_OPEN_DELAY 100000
// Number of retries to check for successful GPIO exports
#define GPIO_SYSFS_OPEN_RETRIES 10
static int linuxgpio_sysfs_setpin(const PROGRAMMER *pgm, int pinfunc, int value) {
if(pinfunc < 0 || pinfunc >= N_PINS)
return -1;
unsigned pin = pgm->pinno[pinfunc];
if(pin & PIN_INVERSE)
value = !value;
pin &= PIN_MASK;
if(pin > PIN_MAX || my.sysfs_fds[pin] < 0)
return -1;
if(write(my.sysfs_fds[pin], value? "1": "0", 1) != 1)
return -1;
if(pgm->ispdelay > 1)
bitbang_delay(pgm->ispdelay);
return 0;
}
static int linuxgpio_sysfs_getpin(const PROGRAMMER *pgm, int pinfunc) {
if(pinfunc < 0 || pinfunc >= N_PINS)
return -1;
unsigned int pin = pgm->pinno[pinfunc];
int invert = !!(pin & PIN_INVERSE);
pin &= PIN_MASK;
if(pin > PIN_MAX || my.sysfs_fds[pin] < 0)
return -1;
if(lseek(my.sysfs_fds[pin], 0, SEEK_SET) < 0)
return -1;
char c;
if(read(my.sysfs_fds[pin], &c, 1) != 1)
return -1;
return c == '0'? 0 + invert: c == '1'? 1 - invert: -1;
}
static int linuxgpio_sysfs_highpulsepin(const PROGRAMMER *pgm, int pinfunc) {
if(pinfunc < 0 || pinfunc >= N_PINS)
return -1;
unsigned int pin = pgm->pinno[pinfunc] & PIN_MASK;
if(pin > PIN_MAX || my.sysfs_fds[pin] < 0)
return -1;
linuxgpio_sysfs_setpin(pgm, pinfunc, 1);
linuxgpio_sysfs_setpin(pgm, pinfunc, 0);
return 0;
}
static void linuxgpio_sysfs_display(const PROGRAMMER *pgm, const char *p) {
msg_info("%sPin assignment : /sys/class/gpio/gpio{n}\n", p);
pgm_display_generic_mask(pgm, p, SHOW_AVR_PINS);
}
static void linuxgpio_enable(PROGRAMMER *pgm, const AVRPART *p) {
}
static void linuxgpio_disable(const PROGRAMMER *pgm) {
}
static void linuxgpio_powerup(const PROGRAMMER *pgm) {
}
static void linuxgpio_powerdown(const PROGRAMMER *pgm) {
}
static int linuxgpio_sysfs_open(PROGRAMMER *pgm, const char *port) {
int r, i, pin;
char gpio_path[60];
struct stat stat_buf;
if(bitbang_check_prerequisites(pgm) < 0)
return -1;
for(i = 0; i < N_GPIO; i++)
my.sysfs_fds[i] = -1;
// Avrdude assumes that if a pin number is invalid it means not used/available
for(i = 1; i < N_PINS; i++) { // The pin enumeration in libavrdude.h starts with PPI_AVR_VCC = 1
if((pgm->pinno[i] & PIN_MASK) <= PIN_MAX) {
pin = pgm->pinno[i] & PIN_MASK;
if((r = linuxgpio_sysfs_export(pin)) < 0) {
pmsg_ext_error("cannot export GPIO %d, already exported/busy?: %s", pin, strerror(errno));
return r;
}
// Wait until GPIO directory appears
snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/gpio%u", pin);
unsigned int retry_count;
for(retry_count = 0; retry_count < GPIO_SYSFS_OPEN_RETRIES; retry_count++) {
int ret = stat(gpio_path, &stat_buf);
if(ret == 0)
break;
if(ret < 0 && errno != ENOENT) {
linuxgpio_sysfs_unexport(pin);
return ret;
}
usleep(GPIO_SYSFS_OPEN_DELAY);
}
/* Write direction, looping in case of EACCES errors due to delayed
* udev permission rule application after export */
for(retry_count = 0; retry_count < GPIO_SYSFS_OPEN_RETRIES; retry_count++) {
usleep(GPIO_SYSFS_OPEN_DELAY);
r = i == PIN_AVR_SDI?
linuxgpio_sysfs_dir_in(pin):
linuxgpio_sysfs_dir_out(pin);
if(r >= 0)
break;
if(errno != EACCES) {
linuxgpio_sysfs_unexport(pin);
return r;
}
}
if(retry_count)
pmsg_notice2("needed %d retr%s for linuxgpio_sysfs_dir_%s(%s)\n",
retry_count, retry_count > 1? "ies": "y", i == PIN_AVR_SDI? "in": "out", avr_pin_name(pin));
if(r < 0) {
linuxgpio_sysfs_unexport(pin);
return r;
}
if((my.sysfs_fds[pin] = linuxgpio_sysfs_openfd(pin)) < 0)
return my.sysfs_fds[pin];
}
}
return 0;
}
static void linuxgpio_sysfs_close(PROGRAMMER *pgm) {
int i, reset_pin;
reset_pin = pgm->pinno[PIN_AVR_RESET] & PIN_MASK;
// First configure all pins as input, except RESET
// This should avoid possible conflicts when AVR firmware starts
for(i = 0; i < N_GPIO; i++) {
if(my.sysfs_fds[i] >= 0 && i != reset_pin) {
close(my.sysfs_fds[i]);
my.sysfs_fds[i] = -1;
linuxgpio_sysfs_dir_in(i);
linuxgpio_sysfs_unexport(i);
}
}
// Configure RESET as input, if there's external pull up it will go high
if(reset_pin <= PIN_MAX && my.sysfs_fds[reset_pin] >= 0) {
close(my.sysfs_fds[reset_pin]);
my.sysfs_fds[reset_pin] = -1;
linuxgpio_sysfs_dir_in(reset_pin);
linuxgpio_sysfs_unexport(reset_pin);
}
}
void linuxgpio_setup(PROGRAMMER *pgm) {
pgm->cookie = mmt_malloc(sizeof(struct pdata));
}
void linuxgpio_teardown(PROGRAMMER *pgm) {
mmt_free(pgm->cookie);
pgm->cookie = NULL;
}
static int linuxgpio_parseexitspecs(PROGRAMMER *pgm, const char *sp) {
char *cp, *s, *str = mmt_strdup(sp);
int rv = 0;
bool help = false;
s = str;
while((cp = strtok(s, ","))) {
s = NULL;
if(str_eq(cp, "reset")) {
pgm->exit_reset = EXIT_RESET_ENABLED;
continue;
}
if(str_eq(cp, "noreset")) {
pgm->exit_reset = EXIT_RESET_DISABLED;
continue;
}
if(str_eq(cp, "help")) {
help = true;
rv = LIBAVRDUDE_EXIT_OK;
}
if(!help) {
pmsg_error("invalid exitspec parameter -E %s\n", cp);
rv = -1;
}
msg_error("%s -c %s exitspec parameter options:\n", progname, pgmid);
msg_error(" -E reset Programmer will keep the reset line low after programming session\n");
msg_error(" -E noreset Programmer will keep the reset line high after programming session\n");
msg_error(" -E help Show this help menu and exit\n");
mmt_free(str);
return rv;
}
mmt_free(str);
return rv;
}
// libgpiod backend for the linuxgpio programmer
#ifdef HAVE_LIBGPIOD
#if !HAVE_LIBGPIOD_V1_6 && !HAVE_LIBGPIOD_V2
int gpiod_line_set_direction_input(struct gpiod_line **gpio_line) {
struct gpiod_chip *chip = gpiod_line_get_chip(*gpio_line);
unsigned int gpio_num = gpiod_line_offset(*gpio_line);
// Release pin first...
gpiod_line_release(*gpio_line);
// ... so that we can re-acquire it as input
*gpio_line = gpiod_chip_get_line(chip, gpio_num);
return gpiod_line_request_input(*gpio_line, "avrdude");
}
#endif
#if HAVE_LIBGPIOD_V2
struct gpiod_line {
struct gpiod_chip *chip;
struct gpiod_line_request *line_request;
unsigned int gpio_num;
};
struct gpiod_line *gpiod_line_get(const char *port, int gpio_num) {
struct gpiod_line *rv;
char abs_port[32];
if(snprintf(abs_port, sizeof(abs_port), "/dev/%s", port) >= (int) sizeof(abs_port))
return NULL;
rv = mmt_malloc(sizeof(struct gpiod_line));
rv->gpio_num = gpio_num;
rv->chip = gpiod_chip_open(abs_port);
if(!rv->chip) {
mmt_free(rv);
return NULL;
}
return rv;
}
int gpiod_line_request_input(struct gpiod_line *gpio_line, const char *consumer) {
struct gpiod_line_settings *line_settings = NULL;
struct gpiod_line_config *line_config = NULL;
struct gpiod_request_config *req_cfg = NULL;
int retval = -1;
line_settings = gpiod_line_settings_new();
line_config = gpiod_line_config_new();
req_cfg = gpiod_request_config_new();
if(!line_settings || !line_config || !req_cfg)
goto err_out;
retval = gpiod_line_settings_set_direction(line_settings, GPIOD_LINE_DIRECTION_INPUT);
if(retval != 0)
goto err_out;
retval = gpiod_line_config_add_line_settings(line_config, &gpio_line->gpio_num, 1, line_settings);
if(retval != 0)
goto err_out;
gpiod_request_config_set_consumer(req_cfg, consumer);
gpio_line->line_request = gpiod_chip_request_lines(gpio_line->chip, req_cfg, line_config);
if(!gpio_line->line_request)
goto err_out;
retval = 0;
err_out:
gpiod_line_settings_free(line_settings);
gpiod_line_config_free(line_config);
gpiod_request_config_free(req_cfg);
return retval;
}
int gpiod_line_request_output(struct gpiod_line *gpio_line, const char *consumer, int value) {
struct gpiod_line_settings *line_settings = NULL;
struct gpiod_line_config *line_config = NULL;
struct gpiod_request_config *req_cfg = NULL;
int retval = -1;
line_settings = gpiod_line_settings_new();
line_config = gpiod_line_config_new();
req_cfg = gpiod_request_config_new();
if(!line_settings || !line_config || !req_cfg)
goto err_out;
retval = gpiod_line_settings_set_direction(line_settings, GPIOD_LINE_DIRECTION_OUTPUT);
if(retval != 0)
goto err_out;
retval = gpiod_line_settings_set_output_value(line_settings,
value? GPIOD_LINE_VALUE_ACTIVE: GPIOD_LINE_VALUE_INACTIVE);
if(retval != 0)
goto err_out;
retval = gpiod_line_config_add_line_settings(line_config, &gpio_line->gpio_num, 1, line_settings);
if(retval != 0)
goto err_out;
gpiod_request_config_set_consumer(req_cfg, consumer);
gpio_line->line_request = gpiod_chip_request_lines(gpio_line->chip, req_cfg, line_config);
if(!gpio_line->line_request)
goto err_out;
retval = 0;
err_out:
gpiod_line_settings_free(line_settings);
gpiod_line_config_free(line_config);
gpiod_request_config_free(req_cfg);
return retval;
}
int gpiod_line_set_direction_input(struct gpiod_line *gpio_line) {
struct gpiod_line_settings *line_settings = NULL;
struct gpiod_line_config *line_config = NULL;
int retval = -1;
line_settings = gpiod_line_settings_new();
line_config = gpiod_line_config_new();
if(!line_settings || !line_config)
goto err_out;
retval = gpiod_line_settings_set_direction(line_settings, GPIOD_LINE_DIRECTION_INPUT);
if(retval != 0)
goto err_out;
retval = gpiod_line_config_add_line_settings(line_config, &gpio_line->gpio_num, 1, line_settings);
if(retval != 0)
goto err_out;
retval = gpiod_line_request_reconfigure_lines(gpio_line->line_request, line_config);
err_out:
gpiod_line_settings_free(line_settings);
gpiod_line_config_free(line_config);
return retval;
}
void gpiod_line_release(struct gpiod_line *gpio_line) {
gpiod_line_request_release(gpio_line->line_request);
gpiod_chip_close(gpio_line->chip);
mmt_free(gpio_line);
}
static inline int gpiod_line_set_value(struct gpiod_line *gpio_line, int value) {
return gpiod_line_request_set_value(gpio_line->line_request, gpio_line->gpio_num, value);
}
static inline int gpiod_line_get_value(struct gpiod_line *gpio_line) {
return gpiod_line_request_get_value(gpio_line->line_request, gpio_line->gpio_num);
}
#endif
static inline unsigned int linuxgpio_get_gpio_num(struct gpiod_line *gpio_line) {
#if HAVE_LIBGPIOD_V2
return gpio_line->gpio_num;
#else
return gpiod_line_offset(gpio_line);
#endif
}
struct gpiod_line *linuxgpio_libgpiod_lines[N_PINS];
// Try to tell if libgpiod is going to work.
// Returns True (non-zero) if it looks like libgpiod will work, False
// (zero) if libgpiod will not work.
static int libgpiod_is_working(void) {
char const *filename = "/dev/gpiochip0";
struct gpiod_chip *gpiod_chip_ptr;
gpiod_chip_ptr = gpiod_chip_open(filename);
if(gpiod_chip_ptr == NULL) {
msg_info("failed to open gpiod chip %s: %s\n", filename, strerror(errno));
return 0;
}
gpiod_chip_close(gpiod_chip_ptr);
return 1;
}
static void linuxgpio_libgpiod_display(const PROGRAMMER *pgm, const char *p) {
msg_info("%sPin assignment : libgpiod\n", p);
pgm_display_generic_mask(pgm, p, SHOW_AVR_PINS);
}
static int linuxgpio_libgpiod_open(PROGRAMMER *pgm, const char *port) {
if(pgm->bitclock)
pmsg_warning("-c %s does not support adjustable bitclock speed; ignoring -B\n", pgmid);
if(bitbang_check_prerequisites(pgm) < 0)
return -1;
for(int i = 0; i < N_PINS; ++i)
linuxgpio_libgpiod_lines[i] = NULL;
// Avrdude assumes that if a pin number is invalid it means not used/available
for(int i = 1; i < N_PINS; i++) { // The pin enumeration in libavrdude.h starts with PPI_AVR_VCC = 1
int r;
int gpio_num;
gpio_num = pgm->pinno[i] & PIN_MASK;
if(gpio_num > PIN_MAX)
continue;
linuxgpio_libgpiod_lines[i] = gpiod_line_get(port, gpio_num);
if(linuxgpio_libgpiod_lines[i] == NULL) {
msg_error("failed to open %s line %d: %s\n", port, gpio_num, strerror(errno));
return -1;
}
// Request the pin, select direction
r = i == PIN_AVR_SDI?
gpiod_line_request_input(linuxgpio_libgpiod_lines[i], "avrdude"):
gpiod_line_request_output(linuxgpio_libgpiod_lines[i], "avrdude", 0);
if(r != 0) {
msg_error("failed to request %s line %d: %s\n", port, gpio_num, strerror(errno));
return -1;
}
}
return 0;
}
static void linuxgpio_libgpiod_close(PROGRAMMER *pgm) {
int i;
// First configure all pins as input, except RESET.
// This should avoid possible conflicts when AVR firmware starts.
for(i = 0; i < N_PINS; ++i) {
if(linuxgpio_libgpiod_lines[i] != NULL && i != PIN_AVR_RESET) {
#if HAVE_LIBGPIOD_V1_6 || HAVE_LIBGPIOD_V2
int r = gpiod_line_set_direction_input(linuxgpio_libgpiod_lines[i]);
#else
int r = gpiod_line_set_direction_input(&linuxgpio_libgpiod_lines[i]);
#endif
if(r != 0)
msg_error("failed to set pin %u to input: %s\n",
linuxgpio_get_gpio_num(linuxgpio_libgpiod_lines[i]), strerror(errno));
gpiod_line_release(linuxgpio_libgpiod_lines[i]);
linuxgpio_libgpiod_lines[i] = NULL;
}
}
if(pgm->exit_reset == EXIT_RESET_ENABLED) // Exit with RESET pin high
pgm->setpin(pgm, PIN_AVR_RESET, 1);
else if(pgm->exit_reset == EXIT_RESET_DISABLED) // Exit with RESET pin low
pgm->setpin(pgm, PIN_AVR_RESET, 0);
else { // Exit with RESET pin as input (default behaviour)
if(linuxgpio_libgpiod_lines[PIN_AVR_RESET] != NULL) {
#if HAVE_LIBGPIOD_V1_6 || HAVE_LIBGPIOD_V2
int r = gpiod_line_set_direction_input(linuxgpio_libgpiod_lines[PIN_AVR_RESET]);
#else
int r = gpiod_line_set_direction_input(&linuxgpio_libgpiod_lines[PIN_AVR_RESET]);
#endif
if(r != 0) {
msg_error("failed to set pin %u to input: %s\n",
linuxgpio_get_gpio_num(linuxgpio_libgpiod_lines[PIN_AVR_RESET]), strerror(errno));
}
gpiod_line_release(linuxgpio_libgpiod_lines[PIN_AVR_RESET]);
linuxgpio_libgpiod_lines[PIN_AVR_RESET] = NULL;
}
}
}
static int linuxgpio_libgpiod_setpin(const PROGRAMMER *pgm, int pinfunc, int value) {
if(pinfunc < 0 || pinfunc >= N_PINS) {
return -1;
}
unsigned pin = pgm->pinno[pinfunc];
if(pin & PIN_INVERSE) {
value = !value;
}
pin &= PIN_MASK;
if(pin > PIN_MAX || linuxgpio_libgpiod_lines[pinfunc] == NULL) {
return -1;
}
int r = gpiod_line_set_value(linuxgpio_libgpiod_lines[pinfunc], value);
if(r != 0) {
msg_error("failed to set value of %s (%u) to %d: %s\n", avr_pin_name(pinfunc),
linuxgpio_get_gpio_num(linuxgpio_libgpiod_lines[pinfunc]), value, strerror(errno));
return -1;
}
if(pgm->ispdelay > 1)
bitbang_delay(pgm->ispdelay);
return 0;
}
static int linuxgpio_libgpiod_getpin(const PROGRAMMER *pgm, int pinfunc) {
if(pinfunc < 0 || pinfunc >= N_PINS) {
return -1;
}
unsigned int pin = pgm->pinno[pinfunc];
int invert = !!(pin & PIN_INVERSE);
pin &= PIN_MASK;
if(pin > PIN_MAX || linuxgpio_libgpiod_lines[pinfunc] == NULL) {
return -1;
}
int r = gpiod_line_get_value(linuxgpio_libgpiod_lines[pinfunc]);
if(r == -1) {
msg_error("failed to read %u: %s\n", linuxgpio_get_gpio_num(linuxgpio_libgpiod_lines[pinfunc]), strerror(errno));
return -1;
}
return r ^ invert;
}
static int linuxgpio_libgpiod_highpulsepin(const PROGRAMMER *pgm, int pinfunc) {
if(pinfunc < 0 || pinfunc >= N_PINS) {
return -1;
}
unsigned int pin = pgm->pinno[pinfunc] & PIN_MASK;
if(pin > PIN_MAX || linuxgpio_libgpiod_lines[pinfunc] == NULL) {
return -1;
}
int r = gpiod_line_set_value(linuxgpio_libgpiod_lines[pinfunc], 1);
if(r != 0) {
msg_error("failed to set value\n");
return -1;
}
r = gpiod_line_set_value(linuxgpio_libgpiod_lines[pinfunc], 0);
if(r != 0) {
msg_error("failed to set value\n");
return -1;
}
return 0;
}
#endif // HAVE_LIBGPIOD
void linuxgpio_initpgm(PROGRAMMER *pgm) {
pgm->ptyp = "linuxgpio";
pgm_fill_old_pins(pgm); // TODO to be removed if old pin data no longer needed
pgm->rdy_led = bitbang_rdy_led;
pgm->err_led = bitbang_err_led;
pgm->pgm_led = bitbang_pgm_led;
pgm->vfy_led = bitbang_vfy_led;
pgm->initialize = bitbang_initialize;
pgm->display = linuxgpio_sysfs_display;
pgm->enable = linuxgpio_enable;
pgm->disable = linuxgpio_disable;
pgm->powerup = linuxgpio_powerup;
pgm->powerdown = linuxgpio_powerdown;
pgm->program_enable = bitbang_program_enable;
pgm->chip_erase = bitbang_chip_erase;
pgm->cmd = bitbang_cmd;
pgm->cmd_tpi = bitbang_cmd_tpi;
pgm->open = linuxgpio_sysfs_open;
pgm->close = linuxgpio_sysfs_close;
pgm->setpin = linuxgpio_sysfs_setpin;
pgm->getpin = linuxgpio_sysfs_getpin;
pgm->highpulsepin = linuxgpio_sysfs_highpulsepin;
pgm->read_byte = avr_read_byte_default;
pgm->write_byte = avr_write_byte_default;
pgm->setup = linuxgpio_setup;
pgm->teardown = linuxgpio_teardown;
pgm->parseexitspecs = linuxgpio_parseexitspecs;
#ifdef HAVE_LIBGPIOD
if(libgpiod_is_working()) {
msg_notice("using libgpiod for linuxgpio\n");
pgm->display = linuxgpio_libgpiod_display;
pgm->open = linuxgpio_libgpiod_open;
pgm->close = linuxgpio_libgpiod_close;
pgm->setpin = linuxgpio_libgpiod_setpin;
pgm->getpin = linuxgpio_libgpiod_getpin;
pgm->highpulsepin = linuxgpio_libgpiod_highpulsepin;
} else {
msg_notice("falling back to sysfs for linuxgpio\n");
}
#endif
}
const char linuxgpio_desc[] = "GPIO bitbanging using the Linux libgpiod or sysfs interface";
#else // ! HAVE_LINUXGPIO
void linuxgpio_initpgm(PROGRAMMER *pgm) {
pmsg_error("Linux libgpiod/sysfs GPIO support not available in this configuration\n");
}
const char linuxgpio_desc[] = "GPIO bitbanging using the Linux libgpiod or sysfs interface (not available)";
#endif // HAVE_LINUXGPIO