/*
 * avrdude - A Downloader/Uploader for AVR device programmers
 * Copyright (C) 2003-2004 Theodore A. Roth <troth@openavr.org>
 * Copyright (C) 2005, 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/>.
 */

/*
 * avrdude interface for the serial programming mode of the Atmel butterfly
 * evaluation board. This board features a bootloader which uses a protocol
 * very similar, but not identical, to the one described in application note
 * avr910.
 *
 * Actually, the butterfly uses a predecessor of the avr910 protocol
 * which is described in application notes avr109 (generic AVR
 * bootloader) and avr911 (opensource programmer).  This file now
 * fully handles the features present in avr109.  It should probably
 * be renamed to avr109, but we rather stick with the old name inside
 * the file.  We'll provide aliases for "avr109" and "avr911" in
 * avrdude.conf so users could call it by these name as well.
 */

#include <ac_cfg.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>

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

#include "butterfly.h"

struct pdata {
  char has_auto_incr_addr;
  unsigned int buffersize;

  int ctype;                    // Cache one byte for flash
  unsigned char cvalue;
  unsigned long caddr;
  bool autoreset;
};

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

// Print error and return when command failed
#define EI(x) do {                 \
  int Eret = (x);                  \
  if(Eret < 0) {                   \
    pmsg_error("%s failed\n", #x); \
    return -1;                     \
  }                                \
} while(0)

#define EV(x) do {                 \
  int Eret = (x);                  \
  if(Eret < 0) {                   \
    pmsg_error("%s failed\n", #x); \
    return;                        \
  }                                \
} while(0)

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

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

static int butterfly_send(const PROGRAMMER *pgm, const char *buf, size_t len) {
  return serial_send(&pgm->fd, (const unsigned char *) buf, len);
}

static int butterfly_recv(const PROGRAMMER *pgm, char *buf, size_t len) {
  return serial_recv(&pgm->fd, (unsigned char *) buf, len);
}

static int butterfly_drain(const PROGRAMMER *pgm, int display) {
  return serial_drain(&pgm->fd, display);
}

static int butterfly_vfy_cmd_sent(const PROGRAMMER *pgm, char *errmsg) {
  char c;

  EI(butterfly_recv(pgm, &c, 1));
  if(c != '\r') {
    pmsg_error("protocol error for command: %s\n", errmsg);
    return -1;
  }

  return 0;
}

static int butterfly_default_led(const PROGRAMMER *pgm, int value) {
  // No LED: do nothing
  return 0;
}

// Issue the 'chip erase' command to the butterfly board
static int butterfly_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) {
  long bak_timeout = serial_recv_timeout;
  AVRMEM *fl = avr_locate_flash(p);
  int ret = 0;

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

  if(serial_recv_timeout < new_timeout)
    serial_recv_timeout = new_timeout;

  EI(butterfly_send(pgm, "e", 1));
  if(butterfly_vfy_cmd_sent(pgm, "chip erase") < 0)
    ret = -1;

  serial_recv_timeout = bak_timeout;
  return ret;
}

static int butterfly_enter_prog_mode(const PROGRAMMER *pgm) {
  EI(butterfly_send(pgm, "P", 1));
  return butterfly_vfy_cmd_sent(pgm, "enter prog mode");
}

static void butterfly_leave_prog_mode(const PROGRAMMER *pgm) {
  EV(butterfly_send(pgm, "L", 1));
  butterfly_vfy_cmd_sent(pgm, "leave prog mode");
}

static int butterfly_program_enable(const PROGRAMMER *pgm, const AVRPART *p) {
  return butterfly_enter_prog_mode(pgm);
}

// Apply power to the AVR processor
static void butterfly_powerup(const PROGRAMMER *pgm) {
  return;
}

// Remove power from the AVR processor
static void butterfly_powerdown(const PROGRAMMER *pgm) {
  return;
}

#define IS_BUTTERFLY_MK 0x0001

// Initialize the AVR device and prepare it to accept commands
static int butterfly_initialize(const PROGRAMMER *pgm, const AVRPART *p) {
  char id[8];
  char sw[2];
  char hw[2];
  char buf[10];
  char type;
  char c, devtype_1st;

  /*
   * Send some ESC to activate butterfly bootloader.  This is not needed for
   * plain avr109 bootloaders but does not harm there either.
   */
  msg_notice("connecting to programmer: ");
  if(pgm->flag & IS_BUTTERFLY_MK) {
    const char * const mk_reset_cmd = "#aR@S\r";
    unsigned char mk_timeout = 0;

    msg_notice(".");
    EI(butterfly_send(pgm, mk_reset_cmd, strlen(mk_reset_cmd)));
    usleep(20000);

    do {
      c = 27;
      EI(butterfly_send(pgm, &c, 1));
      usleep(20000);
      c = 0xaa;
      usleep(80000);
      EI(butterfly_send(pgm, &c, 1));
      if(mk_timeout%10 == 0)
        msg_notice(".");
    } while(mk_timeout++ < 10);

    EI(butterfly_recv(pgm, &c, 1));
    if(c != 'M' && c != '?') {
      msg_error("\n");
      pmsg_error("connection failed");
      return -1;
    } else {
      id[0] = 'M';
      id[1] = 'K';
      id[2] = '2';
      id[3] = 0;
    }
  } else {
    do {
      msg_notice(".");
      EI(butterfly_send(pgm, "\033", 1));
      (void) butterfly_drain(pgm, 0);
      EI(butterfly_send(pgm, "S", 1));
      EI(butterfly_recv(pgm, &c, 1));
      if(c != '?') {
        msg_notice("\n");
        /*
         * Got a useful response, continue getting the programmer identifier.
         * Programmer returns exactly 7 chars _without_ the null.
         */
        id[0] = c;
        EI(butterfly_recv(pgm, &id[1], sizeof(id) - 2));
        id[sizeof(id) - 1] = '\0';
      }
    } while(c == '?');
  }

  // Get the HW and SW versions to see if the programmer is present
  (void) butterfly_drain(pgm, 0);

  EI(butterfly_send(pgm, "V", 1));
  EI(butterfly_recv(pgm, sw, sizeof(sw)));

  EI(butterfly_send(pgm, "v", 1));
  EI(butterfly_recv(pgm, hw, 1));       // First, read only _one_ byte
  if(hw[0] != '?') {
    EI(butterfly_recv(pgm, &hw[1], 1)); // Now, read second byte
  };

  // Get the programmer type (serial or parallel); Expect serial

  EI(butterfly_send(pgm, "p", 1));
  EI(butterfly_recv(pgm, &type, 1));

  msg_notice("Programmer id    = %s; type = %c\n", id, type);
  msg_notice("Software version = %c.%c; ", sw[0], sw[1]);
  if(hw[0] == '?') {
    msg_notice("no hardware version given\n");
  } else {
    msg_notice("Hardware version = %c.%c\n", hw[0], hw[1]);
  };

  // See if programmer supports autoincrement of address

  EI(butterfly_send(pgm, "a", 1));
  EI(butterfly_recv(pgm, &my.has_auto_incr_addr, 1));
  if(my.has_auto_incr_addr == 'Y')
    msg_notice("programmer supports auto addr increment\n");

  // Check support for buffered memory access, abort if not available

  EI(butterfly_send(pgm, "b", 1));
  EI(butterfly_recv(pgm, &c, 1));
  if(c != 'Y') {
    pmsg_notice("buffered memory access not supported; maybe it isn't\n" "a butterfly/AVR109 but a AVR910 device?\n");
    return -1;
  };
  EI(butterfly_recv(pgm, &c, 1));
  my.buffersize = (unsigned int) (unsigned char) c << 8;
  EI(butterfly_recv(pgm, &c, 1));
  my.buffersize += (unsigned int) (unsigned char) c;
  msg_notice("programmer supports buffered memory access with buffersize=%i bytes\n", my.buffersize);

  // Get list of devices that the programmer supports

  EI(butterfly_send(pgm, "t", 1));
  msg_notice2("\nProgrammer supports the following devices:\n");
  devtype_1st = 0;
  while(1) {
    EI(butterfly_recv(pgm, &c, 1));
    if(devtype_1st == 0)
      devtype_1st = c;

    if(c == 0)
      break;
    msg_notice2("    Device code: 0x%02x\n", (unsigned char) c);
  };
  msg_notice2("\n");

  /*
   * Tell the programmer which part we selected. According to the AVR109 code,
   * this is ignored by the bootloader.  As some early versions might not
   * properly ignore it, rather pick up the first device type as reported above
   * than anything out of avrdude.conf, so to avoid a potential conflict.
   * There appears to be no general agreement on AVR910 device IDs beyond the
   * ones from the original appnote 910.
   */

  buf[0] = 'T';
  buf[1] = devtype_1st;

  EI(butterfly_send(pgm, buf, 2));
  if(butterfly_vfy_cmd_sent(pgm, "select device") < 0)
    return -1;

  pmsg_notice("devcode selected: 0x%02x\n", (unsigned) buf[1]);

  if(pgm->program_enable(pgm, p) < 0)
    return -1;
  (void) butterfly_drain(pgm, 0);

  return 0;
}

static void butterfly_disable(const PROGRAMMER *pgm) {
  butterfly_leave_prog_mode(pgm);

  return;
}

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

static int butterfly_open(PROGRAMMER *pgm, const char *port) {
  if(pgm->bitclock)
    pmsg_warning("-c %s does not support adjustable bitclock speed; ignoring -B\n", pgmid);

  pgm->port = port;

  union pinfo pinfo;
  pinfo.serialinfo.baud = pgm->baudrate? pgm->baudrate: 19200;
  pinfo.serialinfo.cflags = SERIAL_8N1;
  int rc;
  if((rc = serial_open(port, pinfo, &pgm->fd)) < 0)
    return rc;

  if(my.autoreset) {
    // This code assumes a negative-logic USB to TTL serial adapter
    // Set RTS/DTR high to discharge the series-capacitor, if present
    pmsg_notice2("toggling the DTR/RTS lines to trigger a hardware reset\n");
    serial_set_dtr_rts(&pgm->fd, 0);
    usleep(250*1000);
    // Pull the RTS/DTR line low to reset AVR
    serial_set_dtr_rts(&pgm->fd, 1);
    // Max 100 us: charging a cap longer creates a high reset spike above Vcc
    usleep(100);
    // Set the RTS/DTR line back to high, so direct connection to reset works
    serial_set_dtr_rts(&pgm->fd, 0);
    usleep(100*1000);
  }
  // Drain any extraneous input
  (void) butterfly_drain(pgm, 0);

  return 0;
}

static void butterfly_close(PROGRAMMER *pgm) {
  // Exit programmer
  EV(butterfly_send(pgm, "E", 1));
  butterfly_vfy_cmd_sent(pgm, "exit bootloader");

  serial_close(&pgm->fd);
  pgm->fd.ifd = -1;
}

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

static void butterfly_set_addr(const PROGRAMMER *pgm, unsigned long addr) {
  if(addr < 0x10000) {
    char cmd[3];

    cmd[0] = 'A';
    cmd[1] = (addr >> 8) & 0xff;
    cmd[2] = addr & 0xff;

    EV(butterfly_send(pgm, cmd, sizeof(cmd)));
    butterfly_vfy_cmd_sent(pgm, "set addr");
  } else {
    char cmd[4];

    cmd[0] = 'H';
    cmd[1] = (addr >> 16) & 0xff;
    cmd[2] = (addr >> 8) & 0xff;
    cmd[3] = addr & 0xff;

    EV(butterfly_send(pgm, cmd, sizeof(cmd)));
    butterfly_vfy_cmd_sent(pgm, "set extaddr");
  }
}

static void butterfly_set_extaddr(const PROGRAMMER *pgm, unsigned long addr) {
  char cmd[4];

  cmd[0] = 'H';
  cmd[1] = (addr >> 16) & 0xff;
  cmd[2] = (addr >> 8) & 0xff;
  cmd[3] = addr & 0xff;

  EV(butterfly_send(pgm, cmd, sizeof(cmd)));
  butterfly_vfy_cmd_sent(pgm, "set extaddr");
}

static int butterfly_write_byte(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned long addr, unsigned char value) {
  char cmd[6];
  int size;

  if(mem_is_flash(m)) {
    int ext_addr = m->op[AVR_OP_LOAD_EXT_ADDR] != NULL;

    my.ctype = 0;               // Invalidate read cache
    cmd[0] = 'B';
    cmd[1] = 0;
    cmd[2] = 2;
    cmd[3] = 'F';
    size = 6;
    (ext_addr? butterfly_set_extaddr: butterfly_set_addr) (pgm, addr >> 1);

    return -1;                  // @@@ not yet implemented (and what about usersig?)
  }

  if(mem_is_eeprom(m)) {
    cmd[0] = 'B';
    cmd[1] = 0;
    cmd[2] = 1;
    cmd[3] = 'E';
    cmd[4] = value;
    size = 5;
    butterfly_set_addr(pgm, addr);
  } else if(mem_is_lock(m)) {
    cmd[0] = 'l';
    cmd[1] = value;
    size = 2;
  } else if(mem_is_readonly(m)) {
    unsigned char is;

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

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

  EI(butterfly_send(pgm, cmd, size));
  if(butterfly_vfy_cmd_sent(pgm, "write byte") < 0)
    return -1;

  return 0;
}

static int butterfly_read_byte_flash(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned long addr, unsigned char *value) {
  int ext_addr = m->op[AVR_OP_LOAD_EXT_ADDR] != NULL;
  char mtype = mem_is_flash(m)? 'F': mem_is_in_sigrow(m)? 'P': mem_is_userrow(m)? 'U': '?';

  addr += avr_sigrow_offset(p, m, addr);

  if(mtype == '?') {
    pmsg_error("cannot read memory %s\n", m->desc);
    return -1;
  }

  if(my.ctype == mtype && my.caddr == addr) {
    *value = my.cvalue;
    return 0;
  }

  char buf[2];                  // Read word and cache the other byte
  char msg[4] = { 'g', 0x00, 0x02, mtype };

  (ext_addr? butterfly_set_extaddr: butterfly_set_addr) (pgm, addr >> 1);
  EI(butterfly_send(pgm, msg, 4));
  EI(butterfly_recv(pgm, buf, sizeof(buf)));

  my.ctype = mtype;
  *value = buf[addr & 1];
  my.cvalue = buf[1 - (addr & 1)];
  my.caddr = addr ^ 1;

  return 0;
}

static int butterfly_read_byte_eeprom(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned long addr, unsigned char *value) {
  butterfly_set_addr(pgm, addr);
  EI(butterfly_send(pgm, "g\000\001E", 4));
  EI(butterfly_recv(pgm, (char *) value, 1));
  return 0;
}

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

  if(mem_is_flash(m) || mem_is_in_sigrow(m) || mem_is_userrow(m)) {
    return butterfly_read_byte_flash(pgm, p, m, addr, value);
  }

  if(mem_is_eeprom(m)) {
    return butterfly_read_byte_eeprom(pgm, p, m, addr, value);
  }

  if(mem_is_lfuse(m)) {
    cmd = 'F';
  } else if(mem_is_hfuse(m)) {
    cmd = 'N';
  } else if(mem_is_efuse(m)) {
    cmd = 'Q';
  } else if(mem_is_lock(m)) {
    cmd = 'r';
  } else
    return -1;

  EI(butterfly_send(pgm, &cmd, 1));
  EI(butterfly_recv(pgm, (char *) value, 1));

  return *value == '?'? -1: 0;
}

static int butterfly_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int addr, unsigned int n_bytes) {
  unsigned int max_addr = addr + n_bytes;
  char *cmd;
  unsigned int blocksize = my.buffersize;
  int ext_addr = m->op[AVR_OP_LOAD_EXT_ADDR] != NULL;
  int isee = mem_is_eeprom(m);

  if(!mem_is_flash(m) && !isee && !mem_is_userrow(m))
    return -2;

  if(isee)                      // Write single bytes to EEPROM
    blocksize = 1;
  else
    my.ctype = 0;               // Invalidate flash byte read cache

  (ext_addr? butterfly_set_extaddr: butterfly_set_addr) (pgm, isee? addr: addr >> 1);

#if 0
  usleep(1000000);
  EI(butterfly_send(pgm, "y", 1));
  if(butterfly_vfy_cmd_sent(pgm, "clear LED") < 0)
    return -1;
#endif

  cmd = mmt_malloc(4 + blocksize);

  cmd[0] = 'B';
  cmd[3] = isee? 'E': mem_is_flash(m)? 'F': 'U';

  while(addr < max_addr) {
    if((max_addr - addr) < blocksize)
      blocksize = max_addr - addr;

    memcpy(&cmd[4], &m->buf[addr], blocksize);
    cmd[1] = (blocksize >> 8) & 0xff;
    cmd[2] = blocksize & 0xff;

    if(butterfly_send(pgm, cmd, 4 + blocksize) < 0 || butterfly_vfy_cmd_sent(pgm, "write block") < 0) {

      mmt_free(cmd);
      return -1;
    }

    addr += blocksize;
  }
  mmt_free(cmd);

  return n_bytes;
}

static int butterfly_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
  unsigned int page_size, unsigned int addr, unsigned int n_bytes) {
  unsigned int max_addr = addr + n_bytes;
  int blocksize = my.buffersize;
  int ext_addr = m->op[AVR_OP_LOAD_EXT_ADDR] != NULL;
  int isee = mem_is_eeprom(m);

  // Only flash, EEPROM or usersig/userrow is allowed
  if(!mem_is_flash(m) && !isee && !mem_is_userrow(m))
    return -2;

  if(isee)                      // Read single bytes from EEPROM
    blocksize = 1;

  char cmd[4];

  cmd[0] = 'g';
  cmd[3] = isee? 'E': mem_is_flash(m)? 'F': 'U';

  (ext_addr? butterfly_set_extaddr: butterfly_set_addr) (pgm, isee? addr: addr >> 1);

  while(addr < max_addr) {
    if((max_addr - addr) < (unsigned int) blocksize)
      blocksize = max_addr - addr;

    cmd[1] = (blocksize >> 8) & 0xff;
    cmd[2] = blocksize & 0xff;

    EI(butterfly_send(pgm, cmd, 4));
    EI(butterfly_recv(pgm, (char *) &m->buf[addr], blocksize));

    addr += blocksize;
  }

  return n_bytes;
}

// Signature byte reads are always 3 bytes
static int butterfly_read_sig_bytes(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m) {
  unsigned char tmp;

  if(m->size < 3) {
    pmsg_error("memsize too small for sig byte read");
    return -1;
  }

  EI(butterfly_send(pgm, "s", 1));
  EI(butterfly_recv(pgm, (char *) m->buf, 3));
  // Returned signature has wrong order
  tmp = m->buf[2];
  m->buf[2] = m->buf[0];
  m->buf[0] = tmp;

  return 3;
}

static int butterfly_parseextparms(const PROGRAMMER *pgm, const LISTID extparms) {
  const char *extended_param;
  int rv = 0;
  bool help = 0;

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

    if(str_eq(extended_param, "autoreset")) {
      my.autoreset = true;
      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 autoreset  Toggle RTS/DTR lines on port open to issue a hardware reset\n");
    msg_error("  -x help       Show this help menu and exit\n");
    return rv;
  }

  return rv;
}

const char butterfly_desc[] = "Atmel Butterfly evaluation board (AVR109, AVR911)";

void butterfly_initpgm(PROGRAMMER *pgm) {
  pgm->ptyp = "butterfly";

  // Mandatory functions
  pgm->rdy_led = butterfly_default_led;
  pgm->err_led = butterfly_default_led;
  pgm->pgm_led = butterfly_default_led;
  pgm->vfy_led = butterfly_default_led;
  pgm->initialize = butterfly_initialize;
  pgm->display = butterfly_display;
  pgm->enable = butterfly_enable;
  pgm->disable = butterfly_disable;
  pgm->powerup = butterfly_powerup;
  pgm->powerdown = butterfly_powerdown;
  pgm->program_enable = butterfly_program_enable;
  pgm->chip_erase = butterfly_chip_erase;
  pgm->open = butterfly_open;
  pgm->close = butterfly_close;
  pgm->read_byte = butterfly_read_byte;
  pgm->write_byte = butterfly_write_byte;

  // Optional functions
  pgm->paged_write = butterfly_paged_write;
  pgm->paged_load = butterfly_paged_load;

  pgm->read_sig_bytes = butterfly_read_sig_bytes;
  pgm->parseextparams = butterfly_parseextparms;
  pgm->setup = butterfly_setup;
  pgm->teardown = butterfly_teardown;
  pgm->flag = 0;
}

const char butterfly_mk_desc[] = "Mikrokopter.de Butterfly";

void butterfly_mk_initpgm(PROGRAMMER *pgm) {
  butterfly_initpgm(pgm);
  pgm->ptyp = "butterfly_mk";
  pgm->flag = IS_BUTTERFLY_MK;
}
