/*
 * avrdude - A Downloader/Uploader for AVR device programmers
 * Copyright (C) 2007 Dick Streefland, adapted for 5.4 by Limor Fried
 *
 * 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/>.
 */

/*
 * Driver for "usbtiny"-type programmers
 * Please see http://www.xs4all.nl/~dicks/avr/usbtiny/
 *        and http://www.ladyada.net/make/usbtinyisp/
 * For example schematics and detailed documentation
 */

#include <ac_cfg.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include <unistd.h>

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

#include "usbtiny.h"
#include "usbdevs.h"

#if defined(HAVE_LIBUSB)        // We use LIBUSB to talk to the board

#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

#include "tpi.h"

#define TPIPCR_GT_0b    0x07
#define TPI_STOP_BITS   0x03

#define LITTLE_TO_BIG_16(x) ((((x) << 8) & 0xFF00) | (((x) >> 8) & 0x00FF))

#ifndef HAVE_UINT_T
typedef unsigned int uint_t;
#endif

#ifndef HAVE_ULONG_T
typedef unsigned long ulong_t;
#endif

struct pdata {
  usb_dev_handle *usb_handle;
  int sck_period;
  int chunk_size;
  int retries;
};

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

// ----------------------------------------------------------------------

static void usbtiny_setup(PROGRAMMER *pgm) {
  pgm->cookie = mmt_malloc(sizeof(struct pdata));
}

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

// Wrapper for simple usb_control_msg messages
static int usb_control(const PROGRAMMER *pgm, unsigned int requestid,
  unsigned int val, unsigned int index, int silent) {

  int nbytes;

  nbytes = usb_control_msg(my.usb_handle,
    USB_ENDPOINT_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, requestid,
    val, index,                 // 2 bytes each of data
    NULL, 0,                    // No data buffer in control message
    USB_TIMEOUT);               // Default timeout
  if(nbytes < 0) {
    cx->usb_access_error = 1;
    if(!silent) {
      msg_error("\n");
      pmsg_error("%s\n", usb_strerror());
    }
    return -1;
  }

  return nbytes;
}

// Wrapper for simple usb_control_msg messages to receive data from programmer
static int usb_in(const PROGRAMMER *pgm,
  unsigned int requestid, unsigned int val, unsigned int index, unsigned char *buffer, int buflen, int bitclk) {
  int nbytes;
  int timeout;
  int i;

  /*
   * Calculate the amount of time we expect the process to take by figuring the
   * bit-clock time and buffer size and adding to the standard USB timeout.
   */
  timeout = USB_TIMEOUT + (buflen*bitclk)/1000;

  for(i = 0; i < 10; i++) {
    nbytes = usb_control_msg(my.usb_handle,
      USB_ENDPOINT_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, requestid, val, index, (char *) buffer, buflen, timeout);
    if(nbytes == buflen) {
      return nbytes;
    }
    my.retries++;
  }
  msg_error("\n");
  pmsg_error("%s (expected %d, got %d)\n", usb_strerror(), buflen, nbytes);
  return -1;
}

// Report the number of retries, and reset the counter.
static void check_retries(const PROGRAMMER *pgm, const char *operation) {
  if(my.retries > 0)
    pmsg_info("%d retries during %s\n", my.retries, operation);
  my.retries = 0;
}

// Wrapper for simple usb_control_msg messages to send data to programmer
static int usb_out(const PROGRAMMER *pgm,
  unsigned int requestid, unsigned int val, unsigned int index, unsigned char *buffer, int buflen, int bitclk) {
  int nbytes;
  int timeout;

  /*
   * Calculate the amount of time we expect the process to take by figuring the
   * bit-clock time and buffer size and adding to the standard USB timeout.
   */
  timeout = USB_TIMEOUT + (buflen*bitclk)/1000;

  nbytes = usb_control_msg(my.usb_handle,
    USB_ENDPOINT_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, requestid, val, index, (char *) buffer, buflen, timeout);
  if(nbytes != buflen) {
    msg_error("\n");
    pmsg_error("%s (expected %d, got %d)\n", usb_strerror(), buflen, nbytes);
    return -1;
  }

  return nbytes;
}

/* Reverse the bits in a byte. Needed since TPI uses little-endian
   bit order (LSB first) whereas SPI uses big-endian (MSB first).*/
static unsigned char reverse(unsigned char b) {
  return ((b & 0x01) << 7)
    | ((b & 0x02) << 5)
    | ((b & 0x04) << 3)
    | ((b & 0x08) << 1)
    | ((b & 0x10) >> 1)
    | ((b & 0x20) >> 3)
    | ((b & 0x40) >> 5)
    | ((b & 0x80) >> 7);
}

// Calculate even parity
static unsigned char tpi_parity(unsigned char b) {
  unsigned char parity = 0;
  int i;

  for(i = 0; i < 8; ++i) {
    if(b & 1)
      parity ^= 1;
    b >>= 1;
  }
  return parity;
}

/*
 * Encode 1 start bit (0), 8 data bits, 1 parity, 2 stop bits (1) inside 16
 * bits. The data is padded to 16 bits by 4 leading 1s (which will be ignored
 * since they're not start bits). This layout enables a write to be followed by
 * a read
 */
static unsigned short tpi_frame(unsigned char b) {
  return LITTLE_TO_BIG_16(0xf000 | (reverse(b) << 3) | tpi_parity(b) << 2 | TPI_STOP_BITS);
}

// Transmit a single byte encapsulated in a 32-bit transfer; unused bits are padded with 1s
static int usbtiny_tpi_tx(const PROGRAMMER *pgm, unsigned char b0) {
  unsigned char res[4];

  if(usb_in(pgm, USBTINY_SPI, tpi_frame(b0), 0xffff, res, sizeof(res), 8*sizeof(res)*my.sck_period) < 0)
    return -1;
  msg_debug("CMD_TPI_TX: [0x%02x]\n", b0);
  return 1;
}

// Transmit a two bytes encapsulated in a 32-bit transfer; unused bits are padded with 1s
static int usbtiny_tpi_txtx(const PROGRAMMER *pgm, unsigned char b0, unsigned char b1) {
  unsigned char res[4];

  if(usb_in(pgm, USBTINY_SPI, tpi_frame(b0), tpi_frame(b1), res, sizeof(res),
    8*sizeof(res)*my.sck_period) < 0) {

    return -1;
  }
  msg_debug("CMD_TPI_TX_TX: [0x%02x 0x%02x]\n", b0, b1);
  return 1;
}

/*
 * Transmit a byte then receive a byte, all encapsulated in a 32-bit transfer.
 * Unused bits are padded with 1s.  This code assumes that the start bit of the
 * byte being received arrives within at most 2 TPICLKs.  We ensure this by
 * calling avr_tpi_program_enable() with delay==TPIPCR_GT_0b.
 */
static int usbtiny_tpi_txrx(const PROGRAMMER *pgm, unsigned char b0) {
  unsigned char res[4], r;
  short w;

  if(usb_in(pgm, USBTINY_SPI, tpi_frame(b0), 0xffff, res, sizeof(res), 8*sizeof(res)*my.sck_period) < 0)
    return -1;

  w = (res[2] << 8) | res[3];
  // Look for start bit (there should be no more than two 1 bits)
  while(w < 0)
    w <<= 1;
  /*
   * Now that we found the start bit, the top 9 bits contain the start bit and
   * the 8 data bits, but the latter in reverse order
   */
  r = reverse(w >> 7);
  if(tpi_parity(r) != ((w >> 6) & 1)) {
    pmsg_error("parity bit is wrong\n");
    return -1;
  }
  if(((w >> 4) & 0x3) != TPI_STOP_BITS) {
    pmsg_error("stop bits not received correctly\n");
    return -1;
  }

  msg_debug("CMD_TPI_TX_RX: [0x%02x -> 0x%02x]\n", b0, r);
  return r;
}

/*
 * Sometimes we just need to know the SPI command for the part to perform a
 * function. Here we wrap this request for an operation so that we can just
 * specify the part and operation and it'll do the right stuff to get the
 * information from AvrDude and send to the USBtiny.
 */
static int usbtiny_avr_op(const PROGRAMMER *pgm, const AVRPART *p, int op, unsigned char *res) {
  unsigned char cmd[4];

  if(p->op[op] == NULL) {
    pmsg_error("operation %d not defined for this chip\n", op);
    return -1;
  }
  memset(cmd, 0, sizeof(cmd));
  avr_set_bits(p->op[op], cmd);

  return pgm->cmd(pgm, cmd, res);
}

// ----------------------------------------------------------------------

// Find a device with the correct VID/PID match for USBtiny

static int usbtiny_open(PROGRAMMER *pgm, const char *port) {
  struct usb_bus *bus;
  struct usb_device *dev = 0;
  const char *bus_name = NULL, *dev_name = NULL;
  int vid, pid;

  pmsg_debug("%s(\"%s\")\n", __func__, port);

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

  // Calculate bus and device names from -P usb:<busdir>:<devicefile> option if present
  if(str_casestarts(port, "usb:")) {
    bus_name = port + 4;
    if((dev_name = strchr(bus_name, ':')))
      dev_name++;
  }

  usb_init();                   // Initialize the libusb system
  usb_find_busses();            // Have libusb scan all the usb buses available
  usb_find_devices();           // Have libusb scan all the usb devices available

  my.usb_handle = NULL;

  if(pgm->usbvid)
    vid = pgm->usbvid;
  else
    vid = USBTINY_VENDOR_DEFAULT;

  LNODEID usbpid = lfirst(pgm->usbpid);

  if(usbpid) {
    pid = *(int *) ldata(usbpid);
    if(lnext(usbpid))
      pmsg_warning("using PID 0x%04x, ignoring remaining PIDs in list\n", pid);
  } else {
    pid = USBTINY_PRODUCT_DEFAULT;
  }

  // Iterate through all the buses and devices
  for(bus = usb_busses; bus; bus = bus->next) {
    for(dev = bus->devices; dev; dev = dev->next) {
      if(dev->descriptor.idVendor == vid && dev->descriptor.idProduct == pid) { // Found match?
        pmsg_notice("found USBtiny with busdir:devicefile = %s:%s\n", bus->dirname, dev->filename);

        // If -P usb:<busdir>:<devicefile> was given, skip non-matching busdir:devicefile
        if(bus_name && dev_name)
          if(!str_busdev_eq(bus->dirname, bus_name) || !str_busdev_eq(dev->filename, dev_name))
            continue;

        my.usb_handle = usb_open(dev); // Attempt to connect to device

        // Wrong permissions or something?
        if(!my.usb_handle) {
          pmsg_warning("cannot open USB device: %s\n", usb_strerror());
          continue;
        }
      }
    }
  }

  if(bus_name && !dev_name) {   // Delayed error message, so found devices are printed with -P usb:xyz
    pmsg_error("invalid -P %s; drop -P option or use -P usb:<busdir>:<devicefile>\n", port);
    return -1;
  }

  if(!my.usb_handle) {
    pmsg_error("cannot find USBtiny device (0x%x/0x%x)\n", vid, pid);
    return -1;
  }

  return 0;                     // If we got here, we must have found a good USB device
}

// Clean up the handle for the usbtiny
static void usbtiny_close(PROGRAMMER *pgm) {
  if(!my.usb_handle)            // Not a valid handle, bail!
    return;
  usb_close(my.usb_handle);     // Ask libusb to clean up
  my.usb_handle = NULL;
}

/* A simple calculator function determines the maximum size of data we can
   shove through a USB connection without getting errors */
static void usbtiny_set_chunk_size(const PROGRAMMER *pgm, int period) {
  my.chunk_size = CHUNK_SIZE;   // Start with the maximum (default)
  while(my.chunk_size > 8 && period > 16) {
    // Reduce the chunk size for a slow SCK to reduce the maximum time of a single USB transfer
    my.chunk_size >>= 1;
    period >>= 1;
  }
}

/* Given a SCK bit-clock speed (in useconds) we verify its an OK speed and tell the
   USBtiny to update itself to the new frequency */
static int usbtiny_set_sck_period(const PROGRAMMER *pgm, double v) {
  my.sck_period = (int) (v*1e6 + 0.5); // Convert from us to int, the 0.5 is for rounding up

  // Make sure its not 0, as that will confuse the usbtiny
  if(my.sck_period < SCK_MIN)
    my.sck_period = SCK_MIN;

  // We can't go slower, due to the byte-size of the clock variable
  if(my.sck_period > SCK_MAX)
    my.sck_period = SCK_MAX;

  pmsg_notice("setting SCK period to %d usec\n", my.sck_period);

  // Send the command to the usbtiny device; MEME: for at90's fix resetstate?
  if(usb_control(pgm, USBTINY_POWERUP, my.sck_period, RESET_LOW, 0) < 0)
    return -1;

  // With the new speed, we'll have to update how much data we send per usb transfer
  usbtiny_set_chunk_size(pgm, my.sck_period);
  return 0;
}

static int usbtiny_initialize(const PROGRAMMER *pgm, const AVRPART *p) {
  unsigned char res[4];         // Store the response from usbtinyisp
  int tries;

  // Check for bit-clock and tell the usbtiny to adjust itself
  if(pgm->bitclock > 0.0) {
    if(!(pgm->extra_features & HAS_BITCLOCK_ADJ))
      pmsg_warning("setting bitclock despite HAS_BITCLOCK_ADJ missing in pgm->extra_features\n");
    // -B option specified: convert to valid range for sck_period
    usbtiny_set_sck_period(pgm, pgm->bitclock);
  } else {
    // -B option not specified: use default
    my.sck_period = SCK_DEFAULT;
    pmsg_notice("using SCK period of %d usec\n", my.sck_period);
    if(usb_control(pgm, USBTINY_POWERUP, my.sck_period, RESET_LOW, 0) < 0)
      return -1;
    usbtiny_set_chunk_size(pgm, my.sck_period);
  }

  // Let the device wake up.
  usleep(50000);

  if(is_tpi(p)) {
    /*
     * Since there is a single TPIDATA line, SDO and SDI must be linked
     * together through a 1kOhm resistor.  Verify that everything we send on
     * SDO gets mirrored back on SDI.
     */
    msg_debug("doing SDO-SDI link check\n");

    memset(res, 0xaa, sizeof(res));
    if(usb_in(pgm, USBTINY_SPI, LITTLE_TO_BIG_16(0x1234), LITTLE_TO_BIG_16(0x5678),
      res, 4, 32*my.sck_period) < 0) {

      pmsg_error("usb_in() failed\n");
      return -1;
    }
    if(res[0] != 0x12 || res[1] != 0x34 || res[2] != 0x56 || res[3] != 0x78) {
      pmsg_error("SDO->SDI check failed (got 0x%02x 0x%02x 0x%02x 0x%02x)\n"
        "\tplease verify that SDI is connected directly to TPIDATA and\n"
        "\tSDO is connected to TPIDATA through a 1kOhm resistor\n", res[0], res[1], res[2], res[3]);
      return -1;
    }

    // Keep TPIDATA high for >= 16 clock cycles:
    if(usb_in(pgm, USBTINY_SPI, 0xffff, 0xffff, res, 4, 32*my.sck_period) < 0) {
      pmsg_error("unable to switch chip into TPI mode\n");
      return -1;
    }
  }

  for(tries = 0; tries < 4; ++tries) {
    if(pgm->program_enable(pgm, p) >= 0)
      break;
    // No response, RESET and try again
    if(usb_control(pgm, USBTINY_POWERUP, my.sck_period, RESET_HIGH, 0) < 0 ||
      usb_control(pgm, USBTINY_POWERUP, my.sck_period, RESET_LOW, 0) < 0)
      return -1;
    usleep(50000);
  }
  if(tries >= 4)
    return -1;
  return 0;
}

static int usbtiny_setpin(const PROGRAMMER *pgm, int pinfunc, int value) {
  // USBtiny is not a bit bang device, but it can set RESET
  if(pinfunc == PIN_AVR_RESET) {
    if(usb_control(pgm, USBTINY_POWERUP, my.sck_period, value? RESET_HIGH: RESET_LOW, 0) < 0)
      return -1;
    usleep(50000);
    return 0;
  }
  return -1;
}

// Tell the USBtiny to release the output pins, etc
static void usbtiny_powerdown(const PROGRAMMER *pgm) {
  if(!my.usb_handle)
    return;                     // Wasn't connected in the first place
  usb_control(pgm, USBTINY_POWERDOWN, 0, 0, 1);
}

/* Send a 4-byte SPI command to the USBtinyISP for execution
   This procedure is used by higher-level Avrdude procedures */
static int usbtiny_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res) {
  int nbytes;

  // Make sure its empty so we don't read previous calls if it fails
  memset(res, '\0', 4);

  nbytes = usb_in(pgm, USBTINY_SPI, (cmd[1] << 8) | cmd[0], // Convert to 16-bit words
    (cmd[3] << 8) | cmd[2],     //  "
    res, 4, 8*my.sck_period);
  if(nbytes < 0)
    return -1;
  check_retries(pgm, "SPI command");
  // Print out the data we sent and received
  msg_debug("CMD: [%02x %02x %02x %02x] [%02x %02x %02x %02x]\n",
    cmd[0], cmd[1], cmd[2], cmd[3], res[0], res[1], res[2], res[3]);

  return nbytes == 4 && res[2] == cmd[1]? LIBAVRDUDE_SUCCESS: LIBAVRDUDE_GENERAL_FAILURE;
}

int usbtiny_cmd_tpi(const PROGRAMMER *pgm, const unsigned char *cmd, int cmd_len, unsigned char *res, int res_len) {
  unsigned char b0, b1;
  int tx, rx, r;

  /*
   * Transmits command two bytes at the time until we're down to 0 or
   * 1 command byte. Then we're either done or we transmit the final
   * byte optionally followed by reading 1 byte. With the current TPI
   * protocol, we never receive more than one byte.
   */
  for(tx = rx = 0; tx < cmd_len;) {
    b0 = cmd[tx++];
    if(tx < cmd_len) {
      b1 = cmd[tx++];
      if(usbtiny_tpi_txtx(pgm, b0, b1) < 0)
        return -1;
    } else {
      if(res_len > 0) {
        if((r = usbtiny_tpi_txrx(pgm, b0)) < 0)
          return -1;
        res[rx++] = r;
      } else {
        if(usbtiny_tpi_tx(pgm, b0) < 0)
          return -1;
      }
    }
  }

  if(rx < res_len) {
    pmsg_error("unexpected cmd_len=%d/res_len=%d\n", cmd_len, res_len);
    return -1;
  }
  return 0;
}

static int usbtiny_spi(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res, int count) {
  int i;

  // Clear the receive buffer so we don't read old data in case of failure
  memset(res, 0, count);

  if(count%4) {
    pmsg_error("direct SPI write must be a multiple of 4 bytes for %s\n", pgm->ptyp);
    return -1;
  }

  for(i = 0; i < count; i += 4) {
    if(usbtiny_cmd(pgm, cmd + i, res + i) < 0) {
      return -1;
    }
  }
  return 0;
}

// Send the chip-erase command
static int usbtiny_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) {
  unsigned char res[4];

  if(is_tpi(p))
    return avr_tpi_chip_erase(pgm, p);

  if(p->op[AVR_OP_CHIP_ERASE] == NULL) {
    pmsg_error("chip erase instruction not defined for part %s\n", p->desc);
    return -1;
  }

  // Get the command for erasing this chip and transmit to avrdude
  if(usbtiny_avr_op(pgm, p, AVR_OP_CHIP_ERASE, res) < 0)
    return -1;

  if(is_spm(pgm)) {             // Talking to bootloader directly
    AVRMEM *fl = avr_locate_flash(p);

    // Estimated time it takes to erase all pages in bootloader
    usleep(p->chip_erase_delay*(fl? fl->num_pages: 999));
  } else
    usleep(p->chip_erase_delay);

  // Prepare for further instruction
  pgm->initialize(pgm, p);

  return 0;
}

// These are required functions but don't actually do anything
static void usbtiny_enable(PROGRAMMER *pgm, const AVRPART *p) {
}

static void usbtiny_disable(const PROGRAMMER *pgm) {
}

static void usbtiny_display(const PROGRAMMER *pgm, const char *p) {
}

/* To speed up programming and reading, we do a 'chunked' read.
 *  We request just the data itself and the USBtiny uses the SPI function
 *  given to read in the data. Much faster than sending a 4-byte SPI request
 *  per byte
*/
static int usbtiny_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int addr, unsigned int n_bytes) {
  unsigned int maxaddr = addr + n_bytes;
  int chunk, function;
  OPCODE *lext, *readop;
  unsigned char cmd[8];

  // First determine what we're doing
  function = mem_is_eeprom(m)? USBTINY_EEPROM_READ: USBTINY_FLASH_READ;

  // paged_load() only called for pages, so OK to set ext addr once at start
  if((lext = m->op[AVR_OP_LOAD_EXT_ADDR])) {
    memset(cmd, 0, sizeof(cmd));
    avr_set_bits(lext, cmd);
    avr_set_addr(lext, cmd, addr/2);
    if(pgm->cmd(pgm, cmd, cmd + 4) < 0)
      return -1;
  }

  // Byte acces as work around to correctly read flash above 64 kiB
  if(function == USBTINY_FLASH_READ && addr >= 0x10000) {
    for(unsigned int i = 0; i < n_bytes; i++, addr++) {
      if(!(readop = m->op[addr & 1? AVR_OP_READ_HI: AVR_OP_READ_LO]))
        return -1;

      memset(cmd, 0, sizeof(cmd));
      avr_set_bits(readop, cmd);
      avr_set_addr(readop, cmd, addr/2);
      if(pgm->cmd(pgm, cmd, cmd + 4) < 0)
        return -1;
      m->buf[addr] = 0;
      avr_get_output(readop, cmd + 4, m->buf + addr);
    }

    return n_bytes;
  }

  for(; addr < maxaddr; addr += chunk) {
    chunk = my.chunk_size;      // Start with the maximum chunk size possible
    if(addr + chunk > maxaddr)
      chunk = maxaddr - addr;

    // Send the chunk of data to the USBtiny with the function we want to perform
    if(usb_in(pgm, function,    // EEPROM or flash
        0,                      // Delay between SPI commands
        addr,                   // Address in memory
        m->buf + addr,          // Pointer to where we store data
        chunk,                  // Number of bytes
        32*my.sck_period) < 0) { // Each byte gets turned into a 4-byte SPI cmd
      // usb_in() multiplies this per byte
      return -1;
    }
  }

  check_retries(pgm, "read");
  return n_bytes;
}

/* To speed up programming and reading, we do a 'chunked' write.
 *  We send just the data itself and the USBtiny uses the SPI function
 *  given to write the data. Much faster than sending a 4-byte SPI request
 *  per byte.
*/
static int usbtiny_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int addr, unsigned int n_bytes) {
  unsigned int maxaddr = addr + n_bytes;
  int chunk;                    // Size of data to write at once
  int next;
  int function;                 // Which SPI command to use
  int delay;                    // Delay required between SPI commands

  // First determine what we're doing
  if(mem_is_flash(m)) {
    function = USBTINY_FLASH_WRITE;
  } else {
    function = USBTINY_EEPROM_WRITE;
  }

  delay = 0;
  if(!m->paged) {
    unsigned int poll_value = (m->readback[1] << 8) | m->readback[0];

    if(!poll_value)
      poll_value = 0xffff;
    if(usb_control(pgm, USBTINY_POLL_BYTES, poll_value, 0, 0) < 0)
      return -1;
    delay = m->max_write_delay;
  }

  for(; addr < maxaddr; addr += chunk) {
    // Start with the max chunk size
    chunk = my.chunk_size;
    if(addr + chunk > maxaddr) {
      chunk = maxaddr - addr;
    }

    // We can only write a page at a time anyways
    if(m->paged && chunk > (int) page_size)
      chunk = page_size;

    if(usb_out(pgm, function,   // Flash or EEPROM
        delay,                  // How much to wait between each byte
        addr,                   // Address in memory
        m->buf + addr,          // Pointer to data
        chunk,                  // Number of bytes to write
        // Each byte gets turned into a 4-byte SPI cmd; usb_out() multiplies this per byte; then add the cmd-delay
        32*my.sck_period + delay) < 0) {
      return -1;
    }

    next = addr + chunk;        // Calculate what address we're at now
    if(m->paged && (next%page_size == 0 || next == (int) maxaddr)) {
      // If we're at a page boundary, send the SPI command to flush it.
      avr_write_page(pgm, p, m, (unsigned long) addr);
    }
  }
  return n_bytes;
}

static int usbtiny_program_enable(const PROGRAMMER *pgm, const AVRPART *p) {
  unsigned char buf[4];

  if(is_tpi(p))
    return avr_tpi_program_enable(pgm, p, TPIPCR_GT_0b);
  else
    return usbtiny_avr_op(pgm, p, AVR_OP_PGM_ENABLE, buf);
}

void usbtiny_initpgm(PROGRAMMER *pgm) {
  pgm->ptyp = "USBtiny";

  // Mandatory Functions
  pgm->initialize = usbtiny_initialize;
  pgm->display = usbtiny_display;
  pgm->enable = usbtiny_enable;
  pgm->disable = usbtiny_disable;
  pgm->program_enable = usbtiny_program_enable;
  pgm->chip_erase = usbtiny_chip_erase;
  pgm->cmd = usbtiny_cmd;
  pgm->cmd_tpi = usbtiny_cmd_tpi;
  pgm->open = usbtiny_open;
  pgm->close = usbtiny_close;
  pgm->read_byte = avr_read_byte_default;
  pgm->write_byte = avr_write_byte_default;

  // Optional Functions
  pgm->powerup = NULL;
  pgm->powerdown = usbtiny_powerdown;
  pgm->paged_load = usbtiny_paged_load;
  pgm->paged_write = usbtiny_paged_write;
  pgm->set_sck_period = usbtiny_set_sck_period;
  pgm->setup = usbtiny_setup;
  pgm->teardown = usbtiny_teardown;
  pgm->setpin = usbtiny_setpin;
  pgm->spi = usbtiny_spi;
}

#else                           // ! HAVE_LIBUSB

// Give a proper error if we were not compiled with libusb

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

  return -1;
}

void usbtiny_initpgm(PROGRAMMER *pgm) {
  pgm->ptyp = "usbtiny";

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

const char usbtiny_desc[] = "Usbtiny programmer (also as bootloading protocol)";
