/*
 * 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 <time.h>
#include <stdio.h>
#include "HardwareProfile.h"
#include "ff.h"

// delay
#define CCLK			(80000000L)		// system clock
#define PBCLK		(CCLK / 2)		// peripheral bus clock
#define SAMPLE_RATE	5000
#define CCLK_US		(PBCLK / 1000 / 1000)	// used for micro second delay
#define CCLK_MS		(PBCLK / 1000)	// used for milli second delay

/*
 * SESSION
 */
struct pickle p;

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

void
gpio_bb_set_run(uint8_t run)
{
    if (run==HIGH) {
      LED_On();    
    } else {
      LED_Off();  
    }
}

void
gpio_bb_set_vpp(uint8_t vpp)
{
    if (vpp==HIGH) {
      VPP_On();    
    } else {
      VPP_Off();  
    }
}

void
gpio_bb_set_pgd(uint8_t pgd)
{
    PGD_Clear();
    if (pgd==HIGH) {
      PGD_On();    
    } else {
      PGD_Off();  
    }
}

void
gpio_bb_set_pgc(uint8_t pgc)
{
    if (pgc==HIGH) {
      PGC_On();    
    } else {
      PGC_Off();  
    }
}

uint8_t
gpio_bb_get_pgd(void)
{
    PGD_Set();
    return PGD_Get();
}

struct io_ops gpio_bb_ops = {
  .set_vpp  = gpio_bb_set_vpp,
  .set_pgd  = gpio_bb_set_pgd,
  .set_pgc  = gpio_bb_set_pgc,
  .get_pgd  = gpio_bb_get_pgd,
};

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

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

/*
 * Config defaults
 */
void
io_config(void)
{
  memset(&p, '\0', 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) {
        __delay_us(10);
        if (run)
          io_set_vpp(VPPHIGH);
        else
          io_set_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;
  
    if (n < 2) {
      while(n){
        n--;
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
      }  
      return;
    }
    if (n < 3) {
      while(n){
        n--;
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");
      }  
      return;
    }
    if (n < 4) {
      while(n){
        n--;
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
      }  
      return;
    }
    if (n < 5) {
      while(n){
        n--;
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
      }  
      return;
    }
    if (n < 6) {
      while(n){
        n--;
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");
      }  
      return;
    }
    if (n < 7) {
      while(n){
        n--;
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");
      }  
      return;
    }
    if (n < 8) {
      while(n){
        n--;
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");
      }  
      return;
    }
    if (n < 9) {
      while(n){
        n--;
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");
      }  
      return;
    }
    if (n < 10) {
      while(n){
        n--;
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
        asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
      }  
      return;
    }
    __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
 */
void
io_clock_bit(uint32_t ldly, uint32_t hdly)
{
  PGC_On(); /* Clock high */
  //__delay_us(hdly);
  asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");
  asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");asm("nop");

  PGC_Off();  /* Clock low */
  //__delay_us(ldly);
}

/*
 * 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
 */
uint32_t
io_first_mask(uint8_t nbits)
{
  if (p.msb_first) {
    return (1U << (nbits - 1));  
  } else {
    return (1 << 0);  
  }
}

/*
 * Return next bit mask
 */
uint32_t
io_next_mask(uint32_t mask)
{
  if (p.msb_first) {
    return (mask >> 1);  
  } else {
    return (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;
  uint32_t i;

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

  for (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;
  }
}


