/*
 * avrdude - A Downloader/Uploader for AVR device programmers
 * Copyright (C) 2006 Thomas Fischl
 * Copyright (C) 2007 Joerg Wunsch <j@uriah.heep.sax.de>
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

/*
 * Interface to the USBasp programmer
 *
 * See http://www.fischl.de/usbasp/
 */

#include <ac_cfg.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>

#include <unistd.h>

#include "avrdude.h"
#include "libavrdude.h"

#include "usbasp.h"
#include "usbdevs.h"

#if defined(HAVE_LIBUSB) || defined(HAVE_LIBUSB_1_0)

#ifdef HAVE_LIBUSB_1_0
#define USE_LIBUSB_1_0
#endif

#if defined(USE_LIBUSB_1_0)

#if defined(HAVE_LIBUSB_1_0_LIBUSB_H)
#include <libusb-1.0/libusb.h>
#else
#include <libusb.h>
#endif
#else

#if defined(HAVE_USB_H)
#include <usb.h>
#elif defined(HAVE_LUSB0_USB_H)
#include <lusb0_usb.h>
#else
#error "libusb needs either <usb.h> or <lusb0_usb.h>"
#endif
#endif

#include <sys/time.h>

// Private data for this programmer
struct pdata {

#ifdef USE_LIBUSB_1_0
  libusb_device_handle *usbhandle;
#else
  usb_dev_handle *usbhandle;
#endif

  int sckfreq_hz;
  unsigned int capabilities;
  int use_tpi;
  int section_e;
  int sck_3mhz;

#ifdef USE_LIBUSB_1_0
  libusb_context *ctx;
  char msg[30];                 // Used in errstr()
#endif

  int USB_init;                 // Used in both usbOpenDevice() variants
};

#define my (*(struct pdata *) (pgm->cookie))

#ifdef USE_LIBUSB_1_0
static const char *errstr(const PROGRAMMER *pgm, int result) {
  char *msg = my.msg;
  size_t msgsiz = sizeof(my.msg);
  int n = 0;

  switch(result) {
  case LIBUSB_SUCCESS:
    return "No error";
  case LIBUSB_ERROR_IO:
    n = EIO;
    break;
  case LIBUSB_ERROR_INVALID_PARAM:
    n = EINVAL;
    break;
  case LIBUSB_ERROR_ACCESS:
    n = EACCES;
    break;
  case LIBUSB_ERROR_NO_DEVICE:
    n = ENXIO;
    break;
  case LIBUSB_ERROR_NOT_FOUND:
    n = ENOENT;
    break;
  case LIBUSB_ERROR_BUSY:
    n = EBUSY;
    break;
  case LIBUSB_ERROR_TIMEOUT:

#ifdef ETIMEDOUT
    n = ETIMEDOUT;
    break;
#else
    return "Operation timed out"
#endif

  case LIBUSB_ERROR_OVERFLOW:

#ifdef EOVERFLOW
    n = EOVERFLOW;
    break;
#else
    return "Value too large to be stored in data type"
#endif

  case LIBUSB_ERROR_PIPE:
    n = EPIPE;
    break;
  case LIBUSB_ERROR_INTERRUPTED:
    n = EINTR;
    break;
  case LIBUSB_ERROR_NO_MEM:
    n = ENOMEM;
    break;
  case LIBUSB_ERROR_NOT_SUPPORTED:
    n = ENOSYS;
    break;
  default:
    snprintf(msg, msgsiz, "Unknown libusb error code %d", result);
    return msg;
  }
  return strerror(n);
}
#endif

// Interface management
static void usbasp_setup(PROGRAMMER *pgm);
static void usbasp_teardown(PROGRAMMER *pgm);
static int usbasp_parseextparms(const PROGRAMMER *pgm, const LISTID extparms);

// Internal functions
static int usbasp_transmit(const PROGRAMMER *pgm, unsigned char receive,
  unsigned char functionid, const unsigned char *send, unsigned char *buffer, int buffersize);

#ifdef USE_LIBUSB_1_0
static int usbOpenDevice(const PROGRAMMER *pgm, libusb_device_handle **device, int vendor,
  const char *vendorName, int product, const char *productName, const char *port);
#else
static int usbOpenDevice(const PROGRAMMER *pgm, usb_dev_handle **device, int vendor,
  const char *vendorName, int product, const char *productName, const char *port);
#endif

// Interface prog
static int usbasp_open(PROGRAMMER *pgm, const char *port);
static void usbasp_close(PROGRAMMER *pgm);

// Dummy functions
static void usbasp_disable(const PROGRAMMER *pgm);
static void usbasp_enable(PROGRAMMER *pgm, const AVRPART *p);
static void usbasp_display(const PROGRAMMER *pgm, const char *p);

// Universal functions
static int usbasp_initialize(const PROGRAMMER *pgm, const AVRPART *p);

// SPI specific functions
static int usbasp_spi_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res);
static int usbasp_spi_program_enable(const PROGRAMMER *pgm, const AVRPART *p);
static int usbasp_spi_chip_erase(const PROGRAMMER *pgm, const AVRPART *p);
static int usbasp_spi_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int addr, unsigned int n_bytes);
static int usbasp_spi_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int addr, unsigned int n_bytes);
static int usbasp_spi_set_sck_period(const PROGRAMMER *pgm, double sckperiod);

// TPI specific functions
static void usbasp_tpi_send_byte(const PROGRAMMER *pgm, uint8_t b);
static int usbasp_tpi_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res);
static int usbasp_tpi_program_enable(const PROGRAMMER *pgm, const AVRPART *p);
static int usbasp_tpi_chip_erase(const PROGRAMMER *pgm, const AVRPART *p);
static int usbasp_tpi_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int addr, unsigned int n_bytes);
static int usbasp_tpi_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int addr, unsigned int n_bytes);
static int usbasp_tpi_set_sck_period(const PROGRAMMER *pgm, double sckperiod);
static int usbasp_tpi_read_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m, unsigned long addr,
  unsigned char *value);
static int usbasp_tpi_write_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m, unsigned long addr,
  unsigned char data);

// Dispatching wrappers

static int usbasp_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res) {
  return my.use_tpi? usbasp_tpi_cmd(pgm, cmd, res): usbasp_spi_cmd(pgm, cmd, res);
}

static int usbasp_program_enable(const PROGRAMMER *pgm, const AVRPART *p) {
  return my.use_tpi? usbasp_tpi_program_enable(pgm, p): usbasp_spi_program_enable(pgm, p);
}

static int usbasp_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) {
  return my.use_tpi? usbasp_tpi_chip_erase(pgm, p): usbasp_spi_chip_erase(pgm, p);
}

static int usbasp_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int addr, unsigned int n_bytes) {

  return my.use_tpi?
    usbasp_tpi_paged_load(pgm, p, m, page_size, addr, n_bytes):
    usbasp_spi_paged_load(pgm, p, m, page_size, addr, n_bytes);
}

static int usbasp_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int addr, unsigned int n_bytes) {

  return my.use_tpi?
    usbasp_tpi_paged_write(pgm, p, m, page_size, addr, n_bytes):
    usbasp_spi_paged_write(pgm, p, m, page_size, addr, n_bytes);
}

static int usbasp_set_sck_period(const PROGRAMMER *pgm, double sckperiod) {
  return my.use_tpi? usbasp_tpi_set_sck_period(pgm, sckperiod): usbasp_spi_set_sck_period(pgm, sckperiod);
}

static int usbasp_read_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned long addr, unsigned char *value) {

  return my.use_tpi?
    usbasp_tpi_read_byte(pgm, p, m, addr, value):
    avr_read_byte_default(pgm, p, m, addr, value);
}

static int usbasp_write_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned long addr, unsigned char data) {

  if(mem_is_readonly(m)) {
    unsigned char is;

    if(pgm->read_byte(pgm, p, m, addr, &is) >= 0 && is == data)
      return 0;

    pmsg_error("cannot write to read-only memory %s of %s\n", m->desc, p->desc);
    return -1;
  }

  return my.use_tpi?
    usbasp_tpi_write_byte(pgm, p, m, addr, data):
    avr_write_byte_default(pgm, p, m, addr, data);
}

// Interface management
static void usbasp_setup(PROGRAMMER *pgm) {
  pgm->cookie = mmt_malloc(sizeof(struct pdata));
}

static void usbasp_teardown(PROGRAMMER *pgm) {
  mmt_free(pgm->cookie);
  pgm->cookie = NULL;
}

static int usbasp_parseextparms(const PROGRAMMER *pgm, const LISTID extparms) {
  int rv = 0;
  bool help = false;

  for(LNODEID ln = lfirst(extparms); ln; ln = lnext(ln)) {
    const char *extended_param = ldata(ln);

    if(str_eq(extended_param, "section_config")) {
      pmsg_notice2("%s(): set section_e to 1 (config section)\n", __func__);
      my.section_e = 1;
      continue;
    }

    if(str_eq(extended_param, "help")) {
      help = true;
      rv = LIBAVRDUDE_EXIT_OK;
    }

    if(!help) {
      pmsg_error("invalid extended parameter -x %s\n", extended_param);
      rv = -1;
    }
    msg_error("%s -c %s extended options:\n", progname, pgmid);
    msg_error("  -x section_config  Erase configuration section only with -e (TPI only)\n");
    msg_error("  -x help            Show this help menu and exit\n");
    return rv;
  }

  return rv;
}

static const char *usbasp_get_funcname(unsigned char functionid) {
  switch(functionid) {
  case USBASP_FUNC_CONNECT:
    return "USBASP_FUNC_CONNECT";
    break;
  case USBASP_FUNC_DISCONNECT:
    return "USBASP_FUNC_DISCONNECT";
    break;
  case USBASP_FUNC_TRANSMIT:
    return "USBASP_FUNC_TRANSMIT";
    break;
  case USBASP_FUNC_READFLASH:
    return "USBASP_FUNC_READFLASH";
    break;
  case USBASP_FUNC_ENABLEPROG:
    return "USBASP_FUNC_ENABLEPROG";
    break;
  case USBASP_FUNC_WRITEFLASH:
    return "USBASP_FUNC_WRITEFLASH";
    break;
  case USBASP_FUNC_READEEPROM:
    return "USBASP_FUNC_READEEPROM";
    break;
  case USBASP_FUNC_WRITEEEPROM:
    return "USBASP_FUNC_WRITEEEPROM";
    break;
  case USBASP_FUNC_SETLONGADDRESS:
    return "USBASP_FUNC_SETLONGADDRESS";
    break;
  case USBASP_FUNC_SETISPSCK:
    return "USBASP_FUNC_SETISPSCK";
    break;
  case USBASP_FUNC_TPI_CONNECT:
    return "USBASP_FUNC_TPI_CONNECT";
    break;
  case USBASP_FUNC_TPI_DISCONNECT:
    return "USBASP_FUNC_TPI_DISCONNECT";
    break;
  case USBASP_FUNC_TPI_RAWREAD:
    return "USBASP_FUNC_TPI_RAWREAD";
    break;
  case USBASP_FUNC_TPI_RAWWRITE:
    return "USBASP_FUNC_TPI_RAWWRITE";
    break;
  case USBASP_FUNC_TPI_READBLOCK:
    return "USBASP_FUNC_TPI_READBLOCK";
    break;
  case USBASP_FUNC_TPI_WRITEBLOCK:
    return "USBASP_FUNC_TPI_WRITEBLOCK";
    break;
  case USBASP_FUNC_GETCAPABILITIES:
    return "USBASP_FUNC_GETCAPABILITIES";
    break;
  default:
    return "Unknown USBASP function";
    break;
  }
}

// Wrapper for usb_control_msg call
static int usbasp_transmit(const PROGRAMMER *pgm,
  unsigned char receive, unsigned char functionid, const unsigned char *send, unsigned char *buffer, int buffersize) {
  int nbytes;

  if(verblevel >= MSG_TRACE) {
    pmsg_trace("usbasp_transmit(\"%s\", 0x%02x, 0x%02x, 0x%02x, 0x%02x)\n",
      usbasp_get_funcname(functionid), send[0], send[1], send[2], send[3]);
    if(!receive && buffersize > 0) {
      int i;

      imsg_trace(" => ");
      for(i = 0; i < buffersize; i++)
        msg_trace("[%02x] ", buffer[i]);
      msg_trace("\n");
    }
  }

#ifdef USE_LIBUSB_1_0
  nbytes = libusb_control_transfer(my.usbhandle,
    (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | (receive << 7)) & 0xff,
    functionid & 0xff,
    ((send[1] << 8) | send[0]) & 0xffff, ((send[3] << 8) | send[2]) & 0xffff, buffer, buffersize & 0xffff, 5000);
  if(nbytes < 0) {
    pmsg_ext_error("%s\n", errstr(pgm, nbytes));
    return -1;
  }
#else
  nbytes = usb_control_msg(my.usbhandle,
    USB_TYPE_VENDOR | USB_RECIP_DEVICE | (receive << 7),
    functionid, (send[1] << 8) | send[0], (send[3] << 8) | send[2], (char *) buffer, buffersize, 5000);
  if(nbytes < 0) {
    pmsg_error("%s\n", usb_strerror());
    return -1;
  }
#endif

  if(verblevel >= MSG_TRACE && receive && nbytes > 0) {
    int i;

    imsg_trace("<= ");
    for(i = 0; i < nbytes; i++)
      msg_trace("[%02x] ", buffer[i]);
    msg_trace("\n");
  }

  return nbytes;
}

static int check_for_port_argument_match(const char *port, const char *bus,
  const char *device, const char *serial_num) {

  pmsg_notice("found USBasp with busdir:devicefile = %s:%s, serial_number = %s\n", bus, device, serial_num);

  if(str_casestarts(port, "usb:")) {
    port += 4;
    const char *dev_name = strchr(port, ':');

    if(dev_name)                // Compare with busdir:devicefile
      return str_busdev_eq(port, bus) && str_busdev_eq(dev_name+1, device);

    // Serial number case
    return *port && str_ends(serial_num, port);
  }
  // Invalid -P option.
  return 0;
}

/*
 * Try to open USB device with given VID, PID, vendor and product name Parts of
 * this function were taken from an example code by OBJECTIVE DEVELOPMENT
 * Software GmbH (www.obdev.at) to meet conditions for shared VID/PID
 */

#ifdef USE_LIBUSB_1_0
static int usbOpenDevice(const PROGRAMMER *pgm, libusb_device_handle **device, int vendor,
  const char *vendorName, int product, const char *productName, const char *port) {

  libusb_device_handle *handle = NULL;
  int errorCode = USB_ERROR_NOTFOUND;
  int j;
  int r;

  if(!my.USB_init) {
    my.USB_init = 1;
    libusb_init(&my.ctx);
  }

  libusb_device **dev_list;
  int dev_list_len = libusb_get_device_list(my.ctx, &dev_list);

  for(j = 0; j < dev_list_len; ++j) {
    libusb_device *dev = dev_list[j];
    struct libusb_device_descriptor descriptor;

    libusb_get_device_descriptor(dev, &descriptor);
    if(descriptor.idVendor == vendor && descriptor.idProduct == product) {
      char string[256];

      // We need to open the device in order to query strings
      r = libusb_open(dev, &handle);
      if(!handle) {
        cx->usb_access_error = 1;
        errorCode = USB_ERROR_ACCESS;
        pmsg_warning("cannot open USB device: %s\n", errstr(pgm, r));
        continue;
      }
      errorCode = 0;
      // Do the names match? If vendorName not given ignore it (any vendor matches)
      r = libusb_get_string_descriptor_ascii(handle, descriptor.iManufacturer & 0xff,
        (unsigned char *) string, sizeof(string));
      if(r < 0) {
        cx->usb_access_error = 1;
        if((vendorName != NULL) && (vendorName[0] != 0)) {
          errorCode = USB_ERROR_IO;
          pmsg_warning("cannot query manufacturer for device: %s\n", errstr(pgm, r));
        }
      } else {
        pmsg_notice2("seen device from vendor >%s<\n", string);
        if(vendorName && vendorName[0] && !str_eq(string, vendorName))
          errorCode = USB_ERROR_NOTFOUND;
      }
      // If productName not given ignore it (any product matches)
      r = libusb_get_string_descriptor_ascii(handle, descriptor.iProduct & 0xff,
        (unsigned char *) string, sizeof(string));
      if(r < 0) {
        cx->usb_access_error = 1;
        if((productName != NULL) && (productName[0] != 0)) {
          errorCode = USB_ERROR_IO;
          pmsg_warning("cannot query product for device: %s\n", errstr(pgm, r));
        }
      } else {
        pmsg_notice2("seen product >%s<\n", string);
        if((productName != NULL) && (productName[0] != 0) && !str_eq(string, productName))
          errorCode = USB_ERROR_NOTFOUND;
      }
      if(errorCode == 0) {
        if(!str_eq(port, DEFAULT_USB)) {
          // -P option given
          libusb_get_string_descriptor_ascii(handle, descriptor.iSerialNumber,
            (unsigned char *) string, sizeof(string));

          char bus_num[21], dev_addr[21];
          sprintf(bus_num, "%03d", libusb_get_bus_number(dev));
          sprintf(dev_addr, "%03d", libusb_get_device_address(dev));

          if(!check_for_port_argument_match(port, bus_num, dev_addr, string))
            errorCode = USB_ERROR_NOTFOUND;
        }
      }
      if(errorCode == 0)
        break;
      libusb_close(handle);
      handle = NULL;
    }
  }
  libusb_free_device_list(dev_list, 1);
  if(handle != NULL) {
    errorCode = 0;
    *device = handle;
  }
  return errorCode;
}
#else
static int usbOpenDevice(const PROGRAMMER *pgm, usb_dev_handle **device, int vendor,
  const char *vendorName, int product, const char *productName, const char *port) {

  struct usb_bus *bus;
  struct usb_device *dev;
  usb_dev_handle *handle = NULL;
  int errorCode = USB_ERROR_NOTFOUND;

  if(!my.USB_init) {
    my.USB_init = 1;
    usb_init();
  }
  usb_find_busses();
  usb_find_devices();
  for(bus = usb_get_busses(); bus; bus = bus->next) {
    for(dev = bus->devices; dev; dev = dev->next) {
      if(dev->descriptor.idVendor == vendor && dev->descriptor.idProduct == product) {
        char string[256];
        int len;

        // We need to open the device in order to query strings
        handle = usb_open(dev);
        if(!handle) {
          cx->usb_access_error = 1;
          errorCode = USB_ERROR_ACCESS;
          pmsg_warning("cannot open USB device: %s\n", usb_strerror());
          continue;
        }
        errorCode = 0;
        // Do the names match? If vendorName not given ignore it (any vendor matches)
        len = usb_get_string_simple(handle, dev->descriptor.iManufacturer, string, sizeof(string));
        if(len < 0) {
          cx->usb_access_error = 1;
          if((vendorName != NULL) && (vendorName[0] != 0)) {
            errorCode = USB_ERROR_IO;
            pmsg_warning("cannot query manufacturer for device: %s\n", usb_strerror());
          }
        } else {
          pmsg_notice2("seen device from vendor >%s<\n", string);
          if((vendorName != NULL) && (vendorName[0] != 0) && !str_eq(string, vendorName))
            errorCode = USB_ERROR_NOTFOUND;
        }
        // If productName not given ignore it (any product matches)
        len = usb_get_string_simple(handle, dev->descriptor.iProduct, string, sizeof(string));
        if(len < 0) {
          cx->usb_access_error = 1;
          if((productName != NULL) && (productName[0] != 0)) {
            errorCode = USB_ERROR_IO;
            pmsg_warning("cannot query product for device: %s\n", usb_strerror());
          }
        } else {
          pmsg_notice2("seen product >%s<\n", string);
          if((productName != NULL) && (productName[0] != 0) && !str_eq(string, productName))
            errorCode = USB_ERROR_NOTFOUND;
        }
        if(errorCode == 0) {
          if(!str_caseeq(port, "usb")) {
            // -P option given
            usb_get_string_simple(handle, dev->descriptor.iSerialNumber, string, sizeof(string));
            if(!check_for_port_argument_match(port, bus->dirname, dev->filename, string))
              errorCode = USB_ERROR_NOTFOUND;
          }
        }
        if(errorCode == 0)
          break;
        usb_close(handle);
        handle = NULL;
      }
    }
    if(handle)
      break;
  }
  if(handle != NULL) {
    errorCode = 0;
    *device = handle;
  }
  return errorCode;
}
#endif

// Interface prog
static int usbasp_open(PROGRAMMER *pgm, const char *port) {
  pmsg_debug("%s(\"%s\")\n", __func__, port);

  if(pgm->bitclock && !(pgm->extra_features & HAS_BITCLOCK_ADJ))
    pmsg_warning("setting bitclock despite HAS_BITCLOCK_ADJ missing in pgm->extra_features\n");

  if(!str_casestarts(port, "usb:") && !str_caseeq(port, "usb")) {
    pmsg_error("invalid -P %s; drop -P option or use -P usb:<busdir>:<devicefile> or -P usb:<serialno>\n", port);
    return -1;
  }

  // usb_init will be done in usbOpenDevice
  LNODEID usbpid = lfirst(pgm->usbpid);
  int pid, vid;

  if(usbpid) {
    pid = *(int *) ldata(usbpid);
    if(lnext(usbpid))
      pmsg_warning("using PID 0x%04x, ignoring remaining PIDs in list\n", pid);
  } else {
    pid = USBASP_SHARED_PID;
  }
  vid = pgm->usbvid? pgm->usbvid: USBASP_SHARED_VID;
  if(usbOpenDevice(pgm, &my.usbhandle, vid, pgm->usbvendor, pid, pgm->usbproduct, port) != 0) {
    if(pgmid_is("usbasp")) {
      // Check if device with old VID/PID is available
      if(usbOpenDevice(pgm, &my.usbhandle, USBASP_OLD_VID, "www.fischl.de",
        USBASP_OLD_PID, "USBasp", port) == 0) {

        cx->usb_access_error = 0;
        pmsg_error("found USBasp with old VID/PID; please update its firmware\n");
        return 0;
      }
      /*
       * Original USBasp is specified in config file, so no need to check it
       * again here; no alternative found => fall through to generic error
       * message
       */
    }

    pmsg_error("cannot find USB device with vid=0x%x pid=0x%x", vid, pid);
    if(pgm->usbvendor && *pgm->usbvendor)
      msg_error(" vendor='%s'", pgm->usbvendor);
    if(pgm->usbproduct && *pgm->usbproduct)
      msg_error(" product='%s'", pgm->usbproduct);
    msg_error("\n");
    return -1;
  }

  return 0;
}

static void usbasp_close(PROGRAMMER *pgm) {
  pmsg_debug("usbasp_close()\n");

  if(my.usbhandle != NULL) {
    unsigned char temp[4];

    memset(temp, 0, sizeof(temp));

    if(my.use_tpi) {
      usbasp_transmit(pgm, 1, USBASP_FUNC_TPI_DISCONNECT, temp, temp, sizeof(temp));
    } else {
      usbasp_transmit(pgm, 1, USBASP_FUNC_DISCONNECT, temp, temp, sizeof(temp));
    }

#ifdef USE_LIBUSB_1_0
    libusb_close(my.usbhandle);
#else
    usb_close(my.usbhandle);
#endif
  }

#ifdef USE_LIBUSB_1_0
  libusb_exit(my.ctx);
#else
  // Nothing for usb 0.1?
#endif
}

static void usbasp_disable(const PROGRAMMER *pgm) {
  return;
}

static void usbasp_enable(PROGRAMMER *pgm, const AVRPART *p) {
  return;
}

static void usbasp_display(const PROGRAMMER *pgm, const char *p) {
  return;
}

// Universal functions: for both SPI and TPI
static int usbasp_initialize(const PROGRAMMER *pgm, const AVRPART *p) {
  unsigned char temp[4];
  unsigned char res[4];
  struct pdata *pdata = &my;

  pmsg_debug("usbasp_initialize()\n");

  // Get capabilities
  memset(temp, 0, sizeof(temp));
  pdata->capabilities = usbasp_transmit(pgm, 1, USBASP_FUNC_GETCAPABILITIES, temp, res, sizeof(res)) == 4?
    res[0] | ((unsigned int) res[1] << 8) | ((unsigned int) res[2] << 16) | ((unsigned int) res[3] << 24): 0;

  pdata->use_tpi = (pdata->capabilities & USBASP_CAP_TPI) && is_tpi(p);
  // Query support for 3 MHz SCK in UsbAsp-flash firmware https://github.com/nofeletru/UsbAsp-flash
  pdata->sck_3mhz = ((pdata->capabilities & USBASP_CAP_3MHZ) != 0)? 1: 0;

  if(pdata->use_tpi) {
    // Calc tpiclk delay
    int dly = 1500000.0*pgm->bitclock;

    if(dly < 1)
      dly = 1;
    else if(dly > 2047)
      dly = 2047;
    temp[0] = dly;
    temp[1] = dly >> 8;

    // Connect
    usbasp_transmit(pgm, 1, USBASP_FUNC_TPI_CONNECT, temp, res, sizeof(res));
  } else {
    // Set sck period
    pgm->set_sck_period(pgm, pgm->bitclock);

    // Connect to target device
    usbasp_transmit(pgm, 1, USBASP_FUNC_CONNECT, temp, res, sizeof(res));
  }

  // Wait, so device is ready to receive commands
  usleep(100000);

  return pgm->program_enable(pgm, p);
}

// SPI specific functions
static int usbasp_spi_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res) {
  pmsg_debug("usbasp_spi_cmd(0x%02x, 0x%02x, 0x%02x, 0x%02x)%s",
    cmd[0], cmd[1], cmd[2], cmd[3], verblevel >= MSG_TRACE? " ...\n": "");

  int nbytes = usbasp_transmit(pgm, 1, USBASP_FUNC_TRANSMIT, cmd, res, 4);

  if(nbytes != 4) {
    msg_debug("\n");

    pmsg_error("wrong response size\n");
    return -1;
  }
  pmsg_trace("usbasp_spi_cmd()");
  msg_debug(" => 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", res[0], res[1], res[2], res[3]);

  return 0;
}

static int usbasp_spi_program_enable(const PROGRAMMER *pgm, const AVRPART *p) {
  unsigned char res[4];
  unsigned char cmd[4];

  memset(cmd, 0, sizeof(cmd));
  memset(res, 0, sizeof(res));

  cmd[0] = 0;

  pmsg_debug("usbasp_program_enable()\n");

  int nbytes = usbasp_transmit(pgm, 1, USBASP_FUNC_ENABLEPROG, cmd, res, sizeof(res));

  if((nbytes != 1) | (res[0] != 0)) {
    pmsg_error("program enable: target does not answer (0x%02x)\n", res[0]);
    return -1;
  }

  return 0;
}

static int usbasp_spi_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) {
  unsigned char cmd[4];
  unsigned char res[4];

  pmsg_debug("usbasp_chip_erase()\n");

  if(p->op[AVR_OP_CHIP_ERASE] == NULL) {
    pmsg_error("chip erase instruction not defined for part %s\n", 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;
}

static int usbasp_spi_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int address, unsigned int n_bytes) {

  int n;
  unsigned char cmd[4];
  int wbytes = n_bytes;
  int blocksize;
  unsigned char *buffer = m->buf + address;
  int function;

  pmsg_debug("usbasp_program_paged_load(\"%s\", 0x%x, %d)\n", m->desc, address, n_bytes);

  if(mem_is_flash(m)) {
    function = USBASP_FUNC_READFLASH;
  } else if(mem_is_eeprom(m)) {
    function = USBASP_FUNC_READEEPROM;
  } else {
    return -2;
  }

  // Set blocksize depending on sck frequency
  if((my.sckfreq_hz > 0) && (my.sckfreq_hz < 10000)) {
    blocksize = USBASP_READBLOCKSIZE/10;
  } else {
    blocksize = USBASP_READBLOCKSIZE;
  }

  while(wbytes) {
    if(wbytes <= blocksize) {
      blocksize = wbytes;
    }
    wbytes -= blocksize;

    // Set address (new mode) - if firmware on usbasp support newmode, then they use address from this command
    unsigned char temp[4];

    memset(temp, 0, sizeof(temp));
    cmd[0] = address & 0xFF;
    cmd[1] = address >> 8;
    cmd[2] = address >> 16;
    cmd[3] = address >> 24;
    usbasp_transmit(pgm, 1, USBASP_FUNC_SETLONGADDRESS, cmd, temp, sizeof(temp));

    /* send command with address (compatibility mode) - if firmware on
       usbasp doesn't support newmode, then they use address from this */
    cmd[0] = address & 0xFF;
    cmd[1] = address >> 8;
    // For compatibility: previous version of usbasp.c has not initializes these fields (as FW ignored them)
    cmd[2] = 0;
    cmd[3] = 0;

    n = usbasp_transmit(pgm, 1, function, cmd, buffer, blocksize);

    if(n != blocksize) {
      pmsg_error("wrong reading bytes %x\n", n);
      return -3;
    }

    buffer += blocksize;
    address += blocksize;
  }

  return n_bytes;
}

static int usbasp_spi_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int address, unsigned int n_bytes) {

  int n;
  unsigned char cmd[4];
  int wbytes = n_bytes;
  int blocksize;
  unsigned char *buffer = m->buf + address;
  unsigned char blockflags = USBASP_BLOCKFLAG_FIRST;
  int function;

  pmsg_debug("usbasp_program_paged_write(\"%s\", 0x%x, %d)\n", m->desc, address, n_bytes);

  if(mem_is_flash(m)) {
    function = USBASP_FUNC_WRITEFLASH;
  } else if(mem_is_eeprom(m)) {
    function = USBASP_FUNC_WRITEEEPROM;
  } else {
    return -2;
  }

  // Set blocksize depending on sck frequency
  if((my.sckfreq_hz > 0) && (my.sckfreq_hz < 10000)) {
    blocksize = USBASP_WRITEBLOCKSIZE/10;
  } else {
    blocksize = USBASP_WRITEBLOCKSIZE;
  }

  while(wbytes) {

    if(wbytes <= blocksize) {
      blocksize = wbytes;
    }
    wbytes -= blocksize;

    /* set address (new mode) - if firmware on usbasp support newmode, then
       they use address from this command */
    unsigned char temp[4];

    memset(temp, 0, sizeof(temp));
    cmd[0] = address & 0xFF;
    cmd[1] = address >> 8;
    cmd[2] = address >> 16;
    cmd[3] = address >> 24;
    usbasp_transmit(pgm, 1, USBASP_FUNC_SETLONGADDRESS, cmd, temp, sizeof(temp));

    /* normal command - firmware what support newmode - use address from previous command,
       firmware what doesn't support newmode - ignore previous command and use address from this command */

    cmd[0] = address & 0xFF;
    cmd[1] = address >> 8;
    cmd[2] = page_size & 0xFF;
    cmd[3] = (blockflags & 0x0F) + ((page_size & 0xF00) >> 4); // TP: Mega128 fix
    blockflags = 0;

    n = usbasp_transmit(pgm, 0, function, cmd, buffer, blocksize);

    if(n != blocksize) {
      pmsg_error("wrong count at writing %x\n", n);
      return -3;
    }

    buffer += blocksize;
    address += blocksize;
  }

  return n_bytes;
}

// The list of SCK frequencies in Hz supported by USBasp
static const struct sckoptions usbaspSCKoptions[] = {
  {USBASP_ISP_SCK_3000, 3000000},
  {USBASP_ISP_SCK_1500, 1500000},
  {USBASP_ISP_SCK_750, 750000},
  {USBASP_ISP_SCK_375, 375000},
  {USBASP_ISP_SCK_187_5, 187500},
  {USBASP_ISP_SCK_93_75, 93750},
  {USBASP_ISP_SCK_32, 32000},
  {USBASP_ISP_SCK_16, 16000},
  {USBASP_ISP_SCK_8, 8000},
  {USBASP_ISP_SCK_4, 4000},
  {USBASP_ISP_SCK_2, 2000},
  {USBASP_ISP_SCK_1, 1000},
  {USBASP_ISP_SCK_0_5, 500}
};

/*
 * Set sck period (in seconds)
 * Find next possible sck period and write it to the programmer
 */
static int usbasp_spi_set_sck_period(const PROGRAMMER *pgm, double sckperiod) {
  char clockoption = USBASP_ISP_SCK_AUTO;
  unsigned char res[4];
  unsigned char cmd[4];

  pmsg_debug("usbasp_spi_set_sck_period(%g)\n", sckperiod);

  memset(cmd, 0, sizeof(cmd));
  memset(res, 0, sizeof(res));

  // Reset global sck frequency to auto
  my.sckfreq_hz = 0;

  if(sckperiod == 0) {
    // Auto sck set
    pmsg_notice2("auto set sck period\n");

  } else {

    int sckfreq = 1/sckperiod;        // sck in Hz
    int usefreq = 0;

    pmsg_notice2("try to set SCK period to %g s (= %i Hz)\n", sckperiod, sckfreq);

    // Check if programmer is capable of 3 MHz SCK, if not then ommit 3 MHz setting
    size_t i;

    if(my.sck_3mhz) {
      pmsg_notice2("connected USBasp is capable of 3 MHz SCK\n");
      i = 0;
    } else {
      pmsg_notice2("connected USBasp is not cabable of 3 MHz SCK\n");
      i = 1;
    }
    if(sckfreq >= usbaspSCKoptions[i].frequency) {
      clockoption = usbaspSCKoptions[i].id;
      usefreq = usbaspSCKoptions[i].frequency;
    } else {

      // Find clock option next to given clock

      for(; i < sizeof(usbaspSCKoptions)/sizeof(usbaspSCKoptions[0]); i++) {
        if(sckfreq >= usbaspSCKoptions[i].frequency - 1) {      // Subtract 1 to compensate rounding errors
          clockoption = usbaspSCKoptions[i].id;
          usefreq = usbaspSCKoptions[i].frequency;
          break;
        }
      }
    }

    // Save used sck frequency
    my.sckfreq_hz = usefreq;

    pmsg_info("set SCK frequency to %i Hz\n", usefreq);
  }

  cmd[0] = clockoption;

  int nbytes = usbasp_transmit(pgm, 1, USBASP_FUNC_SETISPSCK, cmd, res, sizeof(res));

  if((nbytes != 1) | (res[0] != 0)) {
    pmsg_error("cannot set sck period; please check for usbasp firmware update\n");
    return -1;
  }

  return 0;
}

// TPI specific functions
static void usbasp_tpi_send_byte(const PROGRAMMER *pgm, uint8_t b) {
  unsigned char temp[4];

  memset(temp, 0, sizeof(temp));

  temp[0] = b;

  usbasp_transmit(pgm, 1, USBASP_FUNC_TPI_RAWWRITE, temp, temp, sizeof(temp));
}

static int usbasp_tpi_recv_byte(const PROGRAMMER *pgm) {
  unsigned char temp[4];

  memset(temp, 0, sizeof(temp));

  if(usbasp_transmit(pgm, 1, USBASP_FUNC_TPI_RAWREAD, temp, temp, sizeof(temp)) != 1) {
    pmsg_error("wrong response size\n");
    return -1;
  }

  return temp[0];
}

static int usbasp_tpi_nvm_waitbusy(const PROGRAMMER *pgm) {
  int retry;

  pmsg_debug("usbasp_tpi_nvm_waitbusy() ...");

  for(retry = 50; retry > 0; retry--) {
    usbasp_tpi_send_byte(pgm, TPI_OP_SIN(NVMCSR));
    if(usbasp_tpi_recv_byte(pgm) & NVMCSR_BSY)
      continue;

    msg_debug(" ready\n");

    return 0;
  }

  msg_debug(" failure\n");

  return -1;
}

static int usbasp_tpi_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res) {
  pmsg_error("spi_cmd used in TPI mode: not allowed\n");
  return -1;
}

static int usbasp_tpi_program_enable(const PROGRAMMER *pgm, const AVRPART *p) {
  int retry;

  pmsg_debug("usbasp_tpi_program_enable()\n");

  // Change guard time
  usbasp_tpi_send_byte(pgm, TPI_OP_SSTCS(TPIPCR));
  usbasp_tpi_send_byte(pgm, TPIPCR_GT_2b);

  // Send SKEY
  usbasp_tpi_send_byte(pgm, 0xE0);
  usbasp_tpi_send_byte(pgm, 0xFF);
  usbasp_tpi_send_byte(pgm, 0x88);
  usbasp_tpi_send_byte(pgm, 0xD8);
  usbasp_tpi_send_byte(pgm, 0xCD);
  usbasp_tpi_send_byte(pgm, 0x45);
  usbasp_tpi_send_byte(pgm, 0xAB);
  usbasp_tpi_send_byte(pgm, 0x89);
  usbasp_tpi_send_byte(pgm, 0x12);

  // Check if device is ready
  for(retry = 0; retry < 10; retry++) {
    usbasp_tpi_send_byte(pgm, TPI_OP_SLDCS(TPIIR));
    if(usbasp_tpi_recv_byte(pgm) != 0x80)
      continue;
    usbasp_tpi_send_byte(pgm, TPI_OP_SLDCS(TPISR));
    if((usbasp_tpi_recv_byte(pgm) & TPISR_NVMEN) == 0)
      continue;
    break;
  }
  if(retry >= 10) {
    pmsg_error("program enable, target does not answer\n");
    return -1;
  }

  return 0;
}

static int usbasp_tpi_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) {
  int pr_0;
  int pr_1;
  int nvm_cmd;

  switch(my.section_e) {
    // Config bits section erase
  case 1:
    pr_0 = 0x41;
    pr_1 = 0x3F;
    nvm_cmd = NVMCMD_SECTION_ERASE;
    pmsg_debug("usbasp_tpi_chip_erase() - section erase\n");
    break;
    // Chip erase (flash only)
  default:
    pr_0 = 0x01;
    pr_1 = 0x40;
    nvm_cmd = NVMCMD_CHIP_ERASE;
    pmsg_debug("usbasp_tpi_chip_erase() - chip erase\n");
    break;
  }

  // Set PR
  usbasp_tpi_send_byte(pgm, TPI_OP_SSTPR(0));
  usbasp_tpi_send_byte(pgm, pr_0);
  usbasp_tpi_send_byte(pgm, TPI_OP_SSTPR(1));
  usbasp_tpi_send_byte(pgm, pr_1);
  // Select what been erase
  usbasp_tpi_send_byte(pgm, TPI_OP_SOUT(NVMCMD));
  usbasp_tpi_send_byte(pgm, nvm_cmd);
  // Dummy write
  usbasp_tpi_send_byte(pgm, TPI_OP_SST_INC);
  usbasp_tpi_send_byte(pgm, 0x00);
  usbasp_tpi_nvm_waitbusy(pgm);

  usleep(p->chip_erase_delay);
  pgm->initialize(pgm, p);

  return 0;
}

static int usbasp_tpi_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int addr, unsigned int n_bytes) {

  unsigned char cmd[4];
  unsigned char *dptr;
  int read, clen, n;
  uint16_t pr;

  pmsg_debug("usbasp_tpi_paged_load(\"%s\", 0x%0x, %d)\n", m->desc, addr, n_bytes);

  dptr = addr + m->buf;
  pr = addr + m->offset;
  read = 0;

  while(read < (int) n_bytes) {
    clen = n_bytes - read;
    if(clen > 32)
      clen = 32;

    // Prepare READBLOCK cmd
    cmd[0] = pr & 0xFF;
    cmd[1] = pr >> 8;
    cmd[2] = 0;
    cmd[3] = 0;
    n = usbasp_transmit(pgm, 1, USBASP_FUNC_TPI_READBLOCK, cmd, dptr, clen);
    if(n != clen) {
      pmsg_error("wrong reading bytes %x\n", n);
      return -3;
    }

    read += clen;
    pr += clen;
    dptr += clen;
  }

  return n_bytes;
}

int usbasp_tpi_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int addr, unsigned int n_bytes) {

  unsigned char cmd[4];
  unsigned char *sptr;
  int written, clen, n;
  uint16_t pr;

  pmsg_debug("usbasp_tpi_paged_write(\"%s\", 0x%0x, %d)\n", m->desc, addr, n_bytes);

  sptr = addr + m->buf;
  pr = addr + m->offset;
  written = 0;

  // Must erase fuse first, TPI parts only have one fuse
  if(mem_is_a_fuse(m)) {        // Keep fuse writing ability for backward compatibility
    // Set PR
    usbasp_tpi_send_byte(pgm, TPI_OP_SSTPR(0));
    usbasp_tpi_send_byte(pgm, (pr & 0xFF) | 1);
    usbasp_tpi_send_byte(pgm, TPI_OP_SSTPR(1));
    usbasp_tpi_send_byte(pgm, (pr >> 8));
    // Select SECTION_ERASE
    usbasp_tpi_send_byte(pgm, TPI_OP_SOUT(NVMCMD));
    usbasp_tpi_send_byte(pgm, NVMCMD_SECTION_ERASE);
    // Dummy write
    usbasp_tpi_send_byte(pgm, TPI_OP_SST_INC);
    usbasp_tpi_send_byte(pgm, 0x00);

    usbasp_tpi_nvm_waitbusy(pgm);
  }

  // Set PR to flash
  usbasp_tpi_send_byte(pgm, TPI_OP_SSTPR(0));
  usbasp_tpi_send_byte(pgm, (pr & 0xFF) | 1);
  usbasp_tpi_send_byte(pgm, TPI_OP_SSTPR(1));
  usbasp_tpi_send_byte(pgm, (pr >> 8));

  while(written < (int) n_bytes) {
    clen = n_bytes - written;
    if(clen > 32)
      clen = 32;

    // Prepare WRITEBLOCK cmd
    cmd[0] = pr & 0xFF;
    cmd[1] = pr >> 8;
    cmd[2] = 0;
    cmd[3] = 0;
    n = usbasp_transmit(pgm, 0, USBASP_FUNC_TPI_WRITEBLOCK, cmd, sptr, clen);
    if(n != clen) {
      pmsg_error("wrong count at writing %x\n", n);
      return -3;
    }

    written += clen;
    pr += clen;
    sptr += clen;
  }

  return n_bytes;
}

static int usbasp_tpi_set_sck_period(const PROGRAMMER *pgm, double sckperiod) {
  return 0;
}

static int usbasp_tpi_read_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m, unsigned long addr,
  unsigned char *value) {
  unsigned char cmd[4];
  int n;
  uint16_t pr;

  pmsg_debug("usbasp_tpi_read_byte(\"%s\", 0x%0lx)\n", m->desc, addr);

  pr = m->offset + addr;

  // READBLOCK
  cmd[0] = pr & 0xFF;
  cmd[1] = pr >> 8;
  cmd[2] = 0;
  cmd[3] = 0;
  n = usbasp_transmit(pgm, 1, USBASP_FUNC_TPI_READBLOCK, cmd, value, 1);
  if(n != 1) {
    if(n >= 0)
      pmsg_error("wrong number %d of bytes read (expected 1)\n", n);
    return -3;
  }
  return 0;
}

static int usbasp_tpi_write_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned long addr, unsigned char data) {
  pmsg_debug("%s(\"%s\", 0x%0lx, 0x%02x)\n", __func__, m->desc, addr, data);

  if(mem_is_flash(m)) {
    pmsg_error("cannot write_byte() to %s; use paged_write()\n", m->desc);
    return -1;
  }
  // Memories left: fuse and lockbits
  if(addr != 0) {
    pmsg_error("unexpected address 0x%04lx of %s memory\n", addr, m->desc);
    return -1;
  }

  uint16_t pr = addr + m->offset;

  usbasp_tpi_nvm_waitbusy(pgm);

  // Must erase fuse first, TPI parts only have one fuse
  if(mem_is_a_fuse(m)) {        // Lockbits are reset during chip erase
    // Set pointer register to pr + 1
    usbasp_tpi_send_byte(pgm, TPI_OP_SSTPR(0));
    usbasp_tpi_send_byte(pgm, (pr & 0xFF) | 1);
    usbasp_tpi_send_byte(pgm, TPI_OP_SSTPR(1));
    usbasp_tpi_send_byte(pgm, (pr >> 8));
    // Select SECTION_ERASE
    usbasp_tpi_send_byte(pgm, TPI_OP_SOUT(NVMCMD));
    usbasp_tpi_send_byte(pgm, NVMCMD_SECTION_ERASE);
    // Dummy write
    usbasp_tpi_send_byte(pgm, TPI_OP_SST_INC);
    usbasp_tpi_send_byte(pgm, 0x00);

    usbasp_tpi_nvm_waitbusy(pgm);
  }
  // Reset pointer register to pr
  usbasp_tpi_send_byte(pgm, TPI_OP_SSTPR(0));
  usbasp_tpi_send_byte(pgm, pr & 0xFF);
  usbasp_tpi_send_byte(pgm, TPI_OP_SSTPR(1));
  usbasp_tpi_send_byte(pgm, (pr >> 8));
  // Select word write
  usbasp_tpi_send_byte(pgm, TPI_OP_SOUT(NVMCMD));
  usbasp_tpi_send_byte(pgm, NVMCMD_WORD_WRITE);
  // Write low byte
  usbasp_tpi_send_byte(pgm, TPI_OP_SST_INC);
  usbasp_tpi_send_byte(pgm, data);
  // Write high byte to start word write
  usbasp_tpi_send_byte(pgm, TPI_OP_SST_INC);
  usbasp_tpi_send_byte(pgm, 0xff);

  usbasp_tpi_nvm_waitbusy(pgm);

  return 0;
}

void usbasp_initpgm(PROGRAMMER *pgm) {
  pgm->ptyp = "usbasp";

  // Mandatory functions
  pgm->initialize = usbasp_initialize;
  pgm->display = usbasp_display;
  pgm->enable = usbasp_enable;
  pgm->disable = usbasp_disable;
  pgm->program_enable = usbasp_program_enable;
  pgm->chip_erase = usbasp_chip_erase;
  pgm->cmd = usbasp_cmd;
  pgm->open = usbasp_open;
  pgm->close = usbasp_close;
  pgm->read_byte = usbasp_read_byte;
  pgm->write_byte = usbasp_write_byte;

  // Optional functions
  pgm->paged_write = usbasp_paged_write;
  pgm->paged_load = usbasp_paged_load;
  pgm->setup = usbasp_setup;
  pgm->teardown = usbasp_teardown;
  pgm->set_sck_period = usbasp_set_sck_period;
  pgm->parseextparams = usbasp_parseextparms;

}

#else                           // HAVE_LIBUSB

static int usbasp_nousb_open(PROGRAMMER *pgm, const char *name) {
  pmsg_error("no usb support; please compile again with libusb installed\n");

  return -1;
}

void usbasp_initpgm(PROGRAMMER *pgm) {
  pgm->ptyp = "usbasp";

  pgm->open = usbasp_nousb_open;
}
#endif                          // HAVE_LIBUSB

const char usbasp_desc[] = "USBasp programmer, see http://www.fischl.de/usbasp/";
