/* -*- c++ -*-

This is Picprog, Microchip PIC programmer software for the serial port device.
Copyright © 1997,2002,2003,2004 Jaakko Hyvätti

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.

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, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

The author may be contacted at:

Email: Jaakko.Hyvatti@iki.fi
URL:   http://www.iki.fi/hyvatti/
Phone: +358 40 5011222

Please send any suggestions, bug reports, success stories etc. to the
Email address above.

*/

#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>

#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <sysexits.h>
#include <string>
#include <sched.h>

#include "picport.h"

using namespace std;

#ifndef O_NONBLOCK
#define O_NONBLOCK O_NDELAY
#endif

unsigned int picport::tsc_1000ns = 0;

// Set this to -1 if you want to use nanosleep when running as root.
// This only works with 2.4 kernels, 2.6 series kernels no more work.
int picport::use_nanosleep = 0;

// Extra delays for long cables, in us
int picport::t_on = 0;
int picport::t_off = 0;
int picport::t_edge = 0;

void
picport::set_clock_data (int rts, int dtr)
{
  // Before first call to set_clock_data, read the modem status like this:
  // ioctl (fd, TIOCMGET, &modembits);
  if (rts)
    modembits |= TIOCM_RTS;
  else
    modembits &= ~TIOCM_RTS;
  if (dtr)
    modembits |= TIOCM_DTR;
  else
    modembits &= ~TIOCM_DTR;
  if (0 > ioctl (fd, TIOCMSET, &modembits)) {
    int e = errno;
    tcsetattr (fd, TCSANOW, &saved);
    cerr << "Unable to set RTS/DTR on tty " << portname << ":" << strerror (e) << endl;
    exit (EX_IOERR);
  }
}

picport::picport (const char *tty)
  : addr (0), debug_on (0)
{
  memset (&W, 0, sizeof (W));
  portname = new char [strlen (tty) + 1];
  strcpy (portname, tty);

  if (0 > (fd = open (tty, O_RDWR|O_NOCTTY|O_NONBLOCK))) {
    int e = errno;
    cerr << "Unable to open tty " << tty << ":" << strerror (e) << endl;
    exit (EX_IOERR);
  }
  tcgetattr (fd, &saved);
  termstate = saved;
  termstate.c_iflag = IGNBRK | IGNPAR;
  termstate.c_oflag = 0;
  termstate.c_cflag = B9600 | CS8 | CREAD | CLOCAL;
  termstate.c_lflag = 0;
  tcsetattr (fd, TCSANOW, &termstate);

  // Initialize lines for programming

  // RTS/DTR low, sleep and then TxD high (program voltage)

  ioctl (fd, TIOCCBRK, 0);
  // Before first call to set_clock_data, read the modem status.
  ioctl (fd, TIOCMGET, &modembits);
  set_clock_data (0, 0);
  usleep (50+t_edge);
  // Check the CTS.  If it is up, even when we just lowered DTR,
  // we probably are not talking to a JDM type programmer.
  int i;
  ioctl (fd, TIOCMGET, &i);
  if (i & TIOCM_CTS) {
    tcsetattr (fd, TCSANOW, &saved);
    cerr << tty << ":CTS is high, probably we are not connected to a programmer but a modem or terminal." << endl;
    exit (EX_IOERR);
  }

  if (use_nanosleep < 0) {
    struct sched_param scp;
    scp.sched_priority = 50;
    if (sched_setscheduler (0, SCHED_FIFO, &scp))
      // Not root.  Cannot use realtime scheduling.
      use_nanosleep = 0;
    else
      use_nanosleep = 1;
  }

#ifdef RDTSC_WORKS
  if (!use_nanosleep && !tsc_1000ns) {
    // Read /proc/cpuinfo for clock speed, or if that fails, try our
    // own test.
    ifstream cpui ("/proc/cpuinfo");
    string w;
    unsigned int mhz = 1; // 1MHz is just a flag that tsc was found
    while (cpui) {
      cpui >> w;
      if ("MHz" == w) {
	unsigned int tmp;
	// Read just the integer part.  No need for the rest.
	cpui >> w >> tmp;
	// Update this when we have 100 GHz CPUs.
	if (tmp >= 4 && tmp <= 100000)
	  mhz = tmp;
      }
      if ("tsc" == w && mhz) {
	// tsc capability found.  Use it.
	tsc_1000ns = mhz;
	break;
      }
    }

    // If the /proc fs did not contain clock speed but indicated
    // tsc capability, test the approximate clock speed.
    if (1 == tsc_1000ns) {
      // Loop the test 20 times, and select the smallest count.
      for (int recount = 0; recount < 20; ++recount) {
	struct timeval tv1, tv2;
	unsigned long a1, d1, a2, d2;

	// Wait for when a microsecond changes
	gettimeofday (&tv2, 0);
	do {
	  gettimeofday (&tv1, 0);
	} while (tv2.tv_usec == tv1.tv_usec);
	asm volatile("rdtsc":"=a" (a1), "=d" (d1));
	tv1.tv_usec += 1000;
	if (tv1.tv_usec >= 1000000) {
	  tv1.tv_usec -= 1000000;
	  tv1.tv_sec ++;
	}
	do {
	  gettimeofday (&tv2, 0);
	} while (tv2.tv_sec < tv1.tv_sec
		 || tv2.tv_sec == tv1.tv_sec && tv2.tv_usec < tv1.tv_usec);
	asm volatile("rdtsc":"=a" (a2), "=d" (d2));
	if (a2 < a1)
	  d2 --;
	a2 -= a1;
	a2 &= 0xffffffff; // for x86_64
	d2 -= d1;
	if (d2)
	  continue;
	a2 /= 1000;
	if (tsc_1000ns <= 1 || tsc_1000ns > a2)
	  tsc_1000ns = a2;
      }
      if (tsc_1000ns <= 1) {
	tcsetattr (fd, TCSANOW, &saved);
	cerr << "Unable to determine CPU clock speed for delay loops" << endl;
	exit (EX_IOERR);
      }
    }
    if (tsc_1000ns >= 1)
      cout << "CPU clock speed: " << tsc_1000ns << " MHz" << endl;
  }
#endif

  if (0 > ioctl (fd, TIOCSBRK, 0)) {
    int e = errno;
    ioctl (fd, TIOCCBRK, 0);
    tcsetattr (fd, TCSANOW, &saved);
    cerr << "Unable to start break on tty " << tty << ":" << strerror (e) << endl;
    exit (EX_IOERR);
  }
  usleep (10+t_off);
}

picport::~picport ()
{
  ioctl (fd, TIOCCBRK, 0);
  usleep (1+t_off);
  tcsetattr (fd, TCSANOW, &saved);
  close (fd);
  delete [] portname;
}

void picport::reset ()
{
  set_clock_data (0, 0);
  ioctl (fd, TIOCCBRK, 0);
  usleep (50+t_off);
  ioctl (fd, TIOCSBRK, 0);
  usleep (10+t_on);
  addr = 0;
}

void picport::delay (long ns)
{
  if (1 == use_nanosleep && !t_edge) {
    timespec ts = {ns / 1000000000, ns % 1000000000}, ts2;
    while (nanosleep (&ts, &ts2) && EINTR == errno)
      ts = ts2;
    return;
  }

#ifdef RDTSC_WORKS
  if (tsc_1000ns > 1 && !t_edge) {
    unsigned long a1, d1, a2, d2;
    asm volatile("rdtsc":"=a" (a1), "=d" (d1));
    d2 = d1;
    if (ns > 10000)
      // This is not as accurate but does not overflow
      a2 = 0xffffffff & (a1 + 1 + (ns+999)/1000*tsc_1000ns);
    else
      a2 = 0xffffffff & (a1 + 1 + (ns*tsc_1000ns + 999) / 1000);
    if (a2 < a1)
      d2++;
    do {
      asm volatile("rdtsc":"=a" (a1), "=d" (d1));
    } while (d1 < d2 || d1 == d2 && a1 < a2);
    return;
  }
  // Fall back to gettimeofday() if tsc is not available.
#endif // RDTSC_WORKS

  // Delay loop that should take more than a microsecond to execute.
  // Check the real time clock also and break out if at least one
  // microsecond has gone.

  struct timeval tv1, tv2;
  volatile int i;
  gettimeofday (&tv1, 0);
  tv2.tv_sec = tv1.tv_sec;
  tv2.tv_usec = 0xffffffff & (tv1.tv_usec + 1 + (ns + 999)/1000+t_edge);
  if (tv2.tv_usec < tv1.tv_usec)
    tv2.tv_sec++;
  for (i = 0; i < 10000 || t_edge; i++) {
    gettimeofday (&tv1, 0);
    if (tv1.tv_sec > tv2.tv_sec
	|| tv1.tv_sec == tv2.tv_sec && tv1.tv_usec >= tv2.tv_usec)
      break;
  }
}

void picport::p_out (int b)
{
  set_clock_data (1, b); // set data, clock up
  delay (1000);
  set_clock_data (0, b); // clock down
  delay (1000);
}

int picport::p_in ()
{
  set_clock_data (1, 1); // clock up
  delay (1000);
  set_clock_data (0, 1); // set data up, clock down
  delay (1000); // 100ns works with all other chips but 16f88 needs 1000ns.

  int i;

  ioctl (fd, TIOCMGET, &i);
  return 0 != (i & TIOCM_CTS);
}

int picport::command18 (enum commands18 comm, int data)
{
  int i, shift = comm;
  if (nop_prog == comm) {
    // A programming command must leave the last bit clock pulse up
    p_out (0);
    p_out (0);
    p_out (0);
    set_clock_data (1, 0); // clock up
    delay (1000*1000); // P9 >1 ms programming time
    set_clock_data (0, 0); // clock down
    // P10 >5 µs high voltage discharge time
    // Later models listed as > 100 µs
    delay (100*1000);
  } else {
    for (i = 0; i < 4; i++)
      p_out ((shift >> i) & 1);
    set_clock_data (0, 0); // set data down
    delay (1000);
  }

  shift = 0; // default return value

  switch (comm) {
  case nop_erase:
    // Erase cycle has delay between command and data
    usleep (10000); // P11 5 ms + P10 5 µs erase time
    // FALLTHROUGH

  case instr:
  case nop_prog:
    if (0x0e00 == (data & 0xff00))
      W[0] = data & 0x00ff;
    else if (0x6ef8 == data)
      addr = (addr & 0x00ffff) | (W[0] << 16);
    else if (0x6ef7 == data)
      addr = (addr & 0xff00ff) | (W[0] << 8);
    else if (0x6ef6 == data)
      addr = (addr & 0xffff00) | W[0];
    goto sw;

  case twrite_dec2:
    addr -= 2;
    goto sw;

  case twrite_inc2:
    addr += 2;
    // FALLTHROUGH

  case twrite:
  case twrite_prog:
  sw:
    for (i = 0; i < 16; i++)
      p_out ((data >> i) & 1);
    set_clock_data (0, 0); // set data down
    break;

  case tread_dec:
    --addr;
    goto sr;

  case tread_inc:
  case inc_tread:
    ++addr;
    // FALLTHROUGH

  case shift_out:
  case tread:
  sr:
    for (i = 0; i < 8; i++)
      p_out(0);
    delay (1000);
    for (i = 0; i < 8; i++)
      shift |= p_in () << i;
    set_clock_data (0, 0); // set data down
  }

  delay (1000);
  return shift;
}

int picport::command30 (enum commands30 comm, int data)
{
  int i, shift = comm;
  for (i = 0; i < 4; i++)
    p_out ((shift >> i) & 1);

  shift = 0; // default return value

  switch (comm) {
  case SIX:

    if (0x200000 == (data & 0xff0000))
      W[data & 15] = (data & 0x0ffff0) >> 4;
    else if (0xEB0000 == (data & 0xfff87f))
      W[(data >> 7) & 15] = 0;
    else if (0xBA1830 == (data & 0xfff870)) {
      // TBLRDL
      ++W[(data >> 7) & 15];
      ++W[data & 15];
    } else if (0xBA0830 == (data & 0xfff870)) {
      // TBLRDL
      ++W[(data >> 7) & 15];
      ++W[data & 15];
    } else if (0x880190 == (data & 0xfffff0))
      addr = (W[data & 15] << 16) & 0xff0000;
    addr = (addr & 0xff0000) | W[6];

    for (i = 0; i < 24; i++)
      p_out ((data >> i) & 1);
    break;

  case REGOUT:
    for (i = 0; i < 8; i++)
      p_out(0);
    for (i = 0; i < 16; i++)
      shift |= p_in () << i;
  }
  set_clock_data (0, 0); // set data down

  return shift;
}

void picport::setaddress (unsigned long a)
{
  if (0 != a && addr == a)
    return;

  command18 (instr, 0x0e00 | ((a & 0xff0000) >> 16));
  command18 (instr, 0x6ef8);
  command18 (instr, 0x0e00 | ((a & 0x00ff00) >> 8));
  command18 (instr, 0x6ef7);
  command18 (instr, 0x0e00 | (a & 0x0000ff));
  command18 (instr, 0x6ef6);
}

void picport::setaddress30 (unsigned long a)
{
  if (0 != a && addr == a)
    return;

  command30 (SIX, 0x200000 | ((a & 0xff0000) >> 12)); // MOV #, W0
  command30 (SIX, 0x880190); // MOV W0, TBLPAG
  command30 (SIX, 0x200006 | ((a & 0x00ffff) << 4)); // MOV #, W6
}

// -1 == error, no programmer present

int picport::command (enum commands comm, int data)
{
  int tmp1, tmp2;

  // first, send out the command, 6 bits

  int i, shift = comm;
  for (i = 0; i < 6; i++)
    p_out ((shift >> i) & 1);
  set_clock_data (0, 0); // set data down

  shift = 0; // default return value

  switch (comm) {
  case inc_addr:
    if (++addr >= 0x4000)
      addr = 0x2000;
    break;

  case data_from_prog:
  case data_from_data:
    delay (1000);
    tmp1 = p_in ();
    for (i = 0; i < 14; i++)
      shift |= p_in () << i;
    tmp2 = p_in ();
    set_clock_data (0, 0); // set data down

    // Start and stop bits must be 1.

    if (!tmp1 || !tmp2) {
      cerr << portname << ":PIC programmer missing or chip fault" << endl;
      return -1;
    }

    if (data_from_data == comm) {

      // Check that the leftover bits were valid, all 1's.
      // This detects if the programmer is not connected to the port.
      // Unfortunately later chips clear these bits, so we must
      // accept both all 1's and all 0's.

      if ((shift & 0x3f00) != 0x3f00
	  && (shift & 0x3f00) != 0x0000) {
	cerr << portname << ": read value "
	     << hex << setfill('0') << setw(4) << shift << dec
	     << ": PIC programmer or chip fault\n"
	  "Is code protection enabled?  "
	  "Use --erase option to disable code protection." << endl;
	return -1;
      }

      shift &= 0xff;
    }
    break;

  case load_conf:
    addr = 0x2000;
    // FALLTHROUGH

  case data_for_prog:
  case data_for_data:
    delay (1000);
    p_out (0);
    for (i = 0; i < 14; i++)
      p_out ((data >> i) & 1);
    p_out (0);
    set_clock_data (0, 0); // set data down
    break;

  default:
    ;
  }

  delay (1000);
  return shift;
}



syntax highlighted by Code2HTML, v. 0.9.1