/*
 * Copyright (C) 2005-2020 Darron Broad
 * All rights reserved.
 *
 * This file is part of Pickle Microchip PIC ICSP.
 *
 * Pickle Microchip PIC ICSP 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.
 *
 * Pickle Microchip PIC ICSP 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 Pickle Microchip PIC ICSP. If not, see http://www.gnu.org/licenses/
 */

#include "pickle.h"
#include <signal.h>
#include <sys/time.h>
bool pgd_out = true;

/*
 * SESSION
 */
extern struct pickle p;
/**
 * @brief Pin function information for a single GPIO pad's RTC functions.
 *
 * This is an internal function of the driver, and is not usually useful
 * for external use.
 */
typedef struct {
    uint32_t reg;       /*!< Register of RTC pad, or 0 if not an RTC GPIO */
    uint32_t mux;       /*!< Bit mask for selecting digital pad or RTC pad */
    uint32_t func;      /*!< Shift of pad function (FUN_SEL) field */
    uint32_t ie;        /*!< Mask of input enable */
    uint32_t pullup;    /*!< Mask of pullup enable */
    uint32_t pulldown;  /*!< Mask of pulldown enable */
    uint32_t slpsel;    /*!< If slpsel bit is set, slpie will be used as pad input enabled signal in sleep mode */
    uint32_t slpie;     /*!< Mask of input enable in sleep mode */
    uint32_t hold;      /*!< Mask of hold enable */
    uint32_t hold_force;/*!< Mask of hold_force bit for RTC IO in RTC_CNTL_HOLD_FORCE_REG */
    uint32_t drv_v;     /*!< Mask of drive capability */
    uint32_t drv_s;     /*!< Offset of drive capability */
    int rtc_num;        /*!< RTC IO number, or -1 if not an RTC GPIO */
} rtc_gpio_desc_t;

/**
 * @brief Provides access to a constant table of RTC I/O pin
 * function information.
 *
 * This is an internal function of the driver, and is not usually useful
 * for external use.
 */
extern const rtc_gpio_desc_t rtc_gpio_desc[GPIO_PIN_COUNT];

/*******************************************************************************
 *
 * I/O operations
 *
 ******************************************************************************/

struct io_ops gpio_bb_ops = {
  .type = NULL,
  .run = NULL,
  .open = NULL,
  .release = NULL,
  .close = NULL,
  .error = NULL,
  .usleep = NULL,
  .set_pgm = NULL,
  .set_vpp  = gpio_bb_set_vpp,
  .set_pgd  = gpio_bb_set_pgd,
  .set_pgc  = gpio_bb_set_pgc,
  .get_pgd  = gpio_bb_get_pgd,
  .configure = NULL,
  .shift_in = NULL,
  .shift_out = NULL,
};

void
io_backend(void)
{
  p.io = &gpio_bb_ops;
}

void
gpio_bb_set_vpp(uint8_t vpp)
{
  if(vpp) {
    GPIO_0to31SET_REG = ((uint32_t)1 << PIN_VPP);
  } else {
    GPIO_0to31CLR_REG = ((uint32_t)1 << PIN_VPP);
  }
}

void
gpio_bb_set_pgd(uint8_t pgd)
{
  if (pgd_out == true) {
    
  } else {
    pinMode(PIN_DATA, OUTPUT);
    pgd_out = true;    
  }
  if(pgd) {
    GPIO_0to31SET_REG = ((uint32_t)1 << PIN_DATA);
  } else {
    GPIO_0to31CLR_REG = ((uint32_t)1 << PIN_DATA);
  }
}

void
gpio_bb_set_pgc(uint8_t pgc)
{
  if(pgc) {
    GPIO_0to31SET_REG = ((uint32_t)1 << PIN_CLOCK);
  } else {
    GPIO_0to31CLR_REG = ((uint32_t)1 << PIN_CLOCK);
  }
}

uint8_t
gpio_bb_get_pgd(void)
{
  if (pgd_out == true) {
    pinMode(PIN_DATA, INPUT);
    pgd_out = false;    
  }
  return (GPIO.in >> PIN_DATA) & 0x1;
}

void
io_signal_off()
{
  signal(SIGINT, SIG_DFL);
  signal(SIGPIPE, SIG_DFL);
}

/*
 * Config defaults
 */
void
io_config(void)
{
  bzero(&p, sizeof(struct pickle));
  p_f = 0;         /* Message output stream    */
  strncpy(p.device, "TTY", STRLEN);   /* Default device           */
  strncpy(p.iface, "/dev/ttyS0", STRLEN);   /* Default interface        */
  p.bitrules = PGD_IN_PULLUP | PGD_OUT_FLIP | /* Default BITRULES for the */
  PGC_OUT_FLIP | VPP_OUT_FLIP | PGD_IN_FLIP;  /*           Velleman K8048 */
  p.sleep_high = 1;       /* Mark time                */
  p.sleep_low = 1;        /* Space time               */
  p.fwsleep = 30;         /* ICSPIO mark/space time   */
  p.baudrate = 115200;        /* STK500v2/SPI baud rate   */
  p.addr = 0x20;          /* I2C/SPI iface address    */
  p.vpp  = 11;          /* TX/!MCLR/VPP             */
  p.pgc  = 10;          /* RTS/PGC CLOCK            */
  p.pgdo = 9;         /* DTR/PGD DATA_OUT         */
  p.pgdi = 9;         /* CTS/PGD DATA_IN          */
  p.pgm  = GPIO_DISABLED;       /* PGM                      */
  p.config = CONFIGVER + CONFIGAND;   /* VERIFY + DUMP            */
}

/*
 * Close
 */
void
io_close(int run)
{
  if (p.iot == IONONE)
    return;

  if (p.io != NULL) {
    if (p.io->release) {
      p.io->release();
    }

    if (p.io->run) {
      if ((p.bitrules & VPP_RELEASE) == 0) {
        ets_delay_us(10);
        if (run)
          digitalWrite(PIN_VPP, VPPHIGH);
        else
          digitalWrite(PIN_VPP, VPPLOW);
      }
    }

    if (p.io->close) {
      p.io->close();
    }

    p.io = NULL;
  }

  p.iot = IONONE;

  io_signal_off();
}

void
io_exit(int err)
{
  fflush(stdout);

  if (p.iot != IONONE)
    io_close(p.bitrules & VPP_RUN);

  exit(err);
}

char *
io_fault(int errorcode)
{
  static char *msg;

  if (errorcode == 0)
    msg = "device not detected";
  else
    msg = "device not compatible";

  return msg;
}


/*
 * Usleep
 */
void
io_usleep(uint32_t n)
{
  /* No sleep */
  if (n == 0)
    return;

  ets_delay_us(n);
}

/*
 * Set PGM (LVP PROGRAM OUT)
 */
void
io_set_pgm(uint8_t pgm)
{
  if (p.io && p.io->set_pgm) {

    if (p.bitrules & PGM_OUT_FLIP)
      pgm = HIGH - pgm;

    p.io->set_pgm(pgm);
  }
}

/*
 * Set VPP (PROGRAM OUT)
 */
void
io_set_vpp(uint8_t vpp)
{
  if (p.io && p.io->set_vpp) {
    p.io->set_vpp(vpp);
  }
}

/*
 * Set PGD (DATA OUT)
 */
void
io_set_pgd(uint8_t pgd)
{
  if (p.io && p.io->set_pgd) {
    p.io->set_pgd(pgd);
  }
}

/*
 * Set PGC (CLOCK OUT)
 */
void
io_set_pgc(uint8_t pgc)
{
  if (p.io && p.io->set_pgc) {
    p.io->set_pgc(pgc);
  }
}

/*
 * Get PGD (DATA IN)
 */
uint8_t
io_get_pgd(void)
{
  uint8_t pgd = 0;

  if (p.io && p.io->get_pgd) {
    pgd = p.io->get_pgd();
  }

  return pgd;
}

/*
 * Configure
 *
 * Clock falling is to satisfy LVP mode PIC24F devices which output data
 * on the falling edge of the clock and not on the rising edge.
 *
 * msb_first is to satisfy the next generation 8-bit chip programming.
 */
void
io_configure(uint8_t clock_falling, uint8_t msb_first)
{
  /* Device configuration */
  p.clock_falling = clock_falling;
  p.msb_first = msb_first;

  /* Back-end configuration */
  if (p.io && p.io->configure)
    p.io->configure();
}

/*
 * Clock bit in or out
 */
inline void
io_clock_bit(uint32_t ldly, uint32_t hdly)
{
  io_set_pgc(HIGH); /* Clock high */
  //io_usleep(hdly);
  //delayMicroseconds(hdly);
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );

  io_set_pgc(LOW);  /* Clock low */
  //io_usleep(ldly);
  //delayMicroseconds(ldly);
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
  asm( "nop \n" );
}

/*
 * Data Input
 *
 * Initialise data input from chip (PGD=IN, SDATA=OUTPUT)
 */
void
io_data_input(void)
{
  if (p.bitrules & PGD_IN_PULLUP) {
    io_set_pgd(HIGH);
  }
  else if (p.bitrules & PGD_IN_PULLDOWN) {
    io_set_pgd(LOW);
  }
}

/*
 * Return first bit mask
 */
inline uint32_t
io_first_mask(uint8_t nbits)
{
  return (p.msb_first) ? (1U << (nbits - 1)) : (1 << 0);
}

/*
 * Return next bit mask
 */
inline uint32_t
io_next_mask(uint32_t mask)
{
  return (p.msb_first) ? (mask >> 1) : (mask << 1);
}

/*
 * Clock in bit(s)
 */
uint32_t
io_clock_in_bits(uint32_t ldly, uint32_t hdly, uint8_t nbits)
{
  uint32_t bits = 0, mask = io_first_mask(nbits);

  /* Initialise for data input */
  io_data_input();

  /* Clock in bits 0..N */
  while (nbits--) {
    if (!p.clock_falling)
      io_clock_bit(ldly, hdly);

    if (io_get_pgd())
      bits |= mask;

    if (p.clock_falling)
      io_clock_bit(ldly, hdly);

    mask = io_next_mask(mask);
  }
  return bits;
}

/*
 * Clock out bit(s)
 */
void
io_clock_out_bits(uint32_t ldly, uint32_t hdly, uint32_t bits, uint8_t nbits)
{
  uint32_t mask = io_first_mask(nbits);

  /* Clock out bits 0..N */
  while (nbits--) {
    io_set_pgd((bits & mask) != 0);
    io_clock_bit(ldly, hdly);

    mask = io_next_mask(mask);
  }
  io_set_pgd(LOW);
}

/*
 * Clock 1 bit in and 2 bits out (2-wire 4-phase)
 */
uint8_t
io_clock_bit_4phase(uint8_t tms, uint8_t tdi)
{
  uint8_t tdo;

  /*********
   * PHASE 1
   */
  io_set_pgd(tdi);  /* TDI OUTPUT */
  io_clock_bit(p.sleep_low, p.sleep_high);

  /*********
   * PHASE 2
   */
  io_set_pgd(tms);  /* TMS OUTPUT */
  io_clock_bit(p.sleep_low, p.sleep_high);

  /*********
   * PHASE 3
   */
  io_data_input();
  io_get_pgd();   /* DISCARD INPUT */
  io_clock_bit(p.sleep_low, p.sleep_high);

  /*********
   * PHASE 4
   */
  tdo = io_get_pgd(); /* TDO INPUT (0 or 1) */
  io_clock_bit(p.sleep_low, p.sleep_high);

  return tdo;
}

/*
 * Clock multiple bits in and multiple bits out (2-wire 4-phase)
 */
uint32_t
io_clock_bits_4phase(uint8_t nbits, uint32_t tms, uint32_t tdi)
{
  uint32_t tdo = 0;

  if (p.busy)
    io_program_feedback('+');

  for (uint32_t i = 0; i < nbits; ++i)
    tdo |= io_clock_bit_4phase((tms >> i) & 1, (tdi >> i) & 1) << i;

  return tdo;
}

/*
 * Program In
 *
 * Shift in 1..N bits for program/verify mode
 */
uint32_t
io_program_in(uint8_t nbits)
{
  //if (p.busy)
  //  io_program_feedback('i');

  //if (p.io && p.io->shift_in)
  //  return p.io->shift_in(nbits);

  return io_clock_in_bits(p.sleep_low, p.sleep_high, nbits);
}

/*
 * Program Out
 *
 * Shift out 1..N bits for program/verify mode
 */
void
io_program_out(uint32_t bits, uint8_t nbits)
{
  //if (p.busy)
  //  io_program_feedback('o');

  //if (p.io && p.io->shift_out)
  //  p.io->shift_out(bits, nbits);
  //else
    io_clock_out_bits(p.sleep_low, p.sleep_high, bits, nbits);
}

/*
 * Busy feedback for program/verify mode
 */
void
io_program_feedback(char c)
{
  static int c_count = 0, a_count = 0;
  static const char arrow[] = "|/-\\";
  static char c_last = 0;

  if ((c_last != c) || (c_count++ > p.busy)) {
    fprintf(stderr, "%c[%c]\r", arrow[a_count++ % strlen(arrow)], c);
    c_last = c;
    c_count = 0;
  }
}
