/*
 * pp.c
 *
 * Programs a PIC16C84 using PIC84PGM hardware.  Based on PIC16C84
 * programming specifications in Microchip data sheet DS30189D.
 *
 * Revision history:
 *
 * ??-Feb-1994: V-0.0; started as a few routines to debug hardware.
 * 07-Mar-1994: V-0.1; first code to successfully program a 16C84.
 * 09-Mar-1994: V-0.2; fuse switches; 7407 support; H/W test.
 * 10-Mar-1994: V-0.3; LPT2, LPT3 and INHX8M support; cosmetic changes.
 * 30-Aug-1996: V-0.4; major re-write (10-Sep-96: added config warning). 
 *
 * Copyright (C) 1994-1996 David Tait.  All rights reserved.
 * Permission is granted to use, modify, or redistribute this software
 * so long as it is not sold or exploited for profit.
 *
 * THIS SOFTWARE IS PROVIDED AS IS AND WITHOUT WARRANTY OF ANY KIND,
 * EITHER EXPRESSED OR IMPLIED.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#ifndef AMIGA
#include <conio.h>
#endif
#include <time.h>
#include "pphw.h"
#include "hex.h"

#define LDCONF  0               /* serial mode commands */
#define LDPROG  2
#define RDPROG  4
#define INCADD  6
#define BEGPRG  8
#define LDDATA  3
#define RDDATA  5
#define ERPROG  9
#define ERDATA  11

#define PRGDLY  TICKS(10000)    /* programming delay (10000us) */

#define PSIZE   1024            /* PIC16C84 parameters */
#define DSIZE   64
#define IBASE   0x2000
#define CBASE   0x2007
#define DBASE   0x2100

#define CP      16              /* config elements */
#define PWRTE   8
#define WDTE    4
#define RC      3
#define HS      2
#define XT      1
#define LP      0

int valid = 0;                  /* true if lpt exists and has H/W */
int setcfg = 0;                 /* true if config set by command-line */
int erase = 0;                  /* erase PIC if true */
int dump = 0;                   /* dump PIC to a file if true */
int verify_only = 0;            /* verify PIC against a file if true */
int wait = 1;                   /* wait for PIC insertion */

char *pname = "pp";
char *does = "PIC16C84 Programmer";
char *version = "Version 0.4b(Ami)";
char *oversion ="Version 0.4";
char *copyright = "Copyright (C) 1997-1998 Nick Waterman";
char *ocopyright ="Copyright (C) 1994-1996 David Tait.";
char *email = "nick-app@leonet.co.uk";


void quit(char *s)
{
    if ( s )
	fprintf(stderr,"%s: %s\n",pname,s);
    if ( valid )
	idle_mode();
    else if ( setup() != -1)
	idle_mode();
    cleanup();
    exit(0);
}


void usage(void)
{
   printf("%s  %s  %s\n",does,version,copyright);
   printf("Based on PP  %s  %s\n\n", oversion, ocopyright);
   printf("Usage: %s  [ -lxhrwpcdev! ]  hexfile\n\n",pname);
   printf("       Config:   l = LP,   x = XT,    h = HS,    r = RC\n");
   printf("                 w = WDTE, p = PWRTE, c = /CP (i.e. protect)\n");
   printf("       Others:   d = dump, e = erase, v = verify only, ");
   printf("! = no wait\n");
   printf("       Defaults: RC, /WDTE, /PWRTE, CP, no erase, wait \n\n");
   printf("Bug reports to %s\n",email);
   quit(NULL);
}


void get_option(char *s)
{
   char *sp;

   for ( sp=s; *sp; ++sp )
      switch ( *sp ) {
	 case 'l':
	 case 'L': config = (config&0x1C) + LP; ++setcfg; break;
	 case 'x':
	 case 'X': config = (config&0x1C) + XT; ++setcfg; break;
	 case 'h':
	 case 'H': config = (config&0x1C) + HS; ++setcfg; break;
	 case 'r':
	 case 'R': config = (config&0x1C) + RC; ++setcfg; break;
	 case 'w':
	 case 'W': config |= WDTE; ++setcfg; break;
	 case 'p':
	 case 'P': config |= PWRTE; ++setcfg; break;
	 case 'c':
	 case 'C': config &= ~CP; ++setcfg; break;
	 case '2':
	 case '3':
	 case '8': break;           /* ignore for compatibility with V-0.3 */
	 case '!': wait = 0; break;
	 case 'e':
	 case 'E': erase = 1; break;
	 case 'd':
	 case 'D': dump = 1; break;
	 case 'v':
	 case 'V': verify_only = 1; break;
	 case '-':
	 case '/': break;
	 default: usage();
      }
}


void prog_cycle(U16 w)
{
    out_word(w);
    command(BEGPRG);
    ms_delay(PRGDLY);
}


void erase_all(void)
{
   int i;

   prog_mode();
   command(LDCONF);             /* defeat code protection */
   out_word(0x3FFF);
   for ( i=0; i<7; ++i )
      command(INCADD);
   command(1);
   command(7);
   command(BEGPRG);
   ms_delay(PRGDLY);
   command(1);
   command(7);

   command(ERPROG);             /* bulk erase program/config memory */
   prog_cycle(0x3FFF);

   command(ERDATA);             /* bulk erase data memory */
   prog_cycle(0x3FFF);
}


void load_conf(int base)
{
    int i, n;

    command(LDCONF);
    out_word(config);

    n = base - IBASE;
    for ( i=0; i<n; ++i )
	command(INCADD);
}


void program(U16 *buf, int n, U16 mask, int ldcmd, int rdcmd, int base)
{
    int i;
    U16 r, w;

    prog_mode();

    if ( base >= IBASE && base <= CBASE )
	load_conf(base);

    for ( i=0; i<n; ++i ) {
	command(rdcmd);
	r = in_word() & mask;
	if ( (w = buf[i]&mask) != r ) {
	    printf("%04X\r",i);
	    command(ldcmd);
	    prog_cycle(w);
	    command(rdcmd);
	    r = in_word() & mask;
	    if ( w != r ) {
		fprintf(stderr,"%s: %04X: read %04X, wanted %04X\n",
						 pname,base+i,r,w);
		quit("Verify failed during programming");
	    }
	}
	command(INCADD);
    }
}


void verify(U16 *buf, int n, U16 mask, int rdcmd, int base)
{
    int i;
    U16 r, w;

    prog_mode();

    if ( base >= IBASE && base <= CBASE )
	load_conf(base);

    for ( i=0; i<n; ++i ) {
	printf("%04X\r",i);
	command(rdcmd);
	r = in_word() & mask;
	if ( (w = buf[i]&mask) != r ) {
	    fprintf(stderr,"%s: %04X: read %04X, wanted %04X\n",
						 pname,base+i,r,w);
	    quit("Verify failed");
	}
	command(INCADD);
    }
}


int read_pic(void)
{
    int i;

    pmlast = -1;
    dmlast = -1;
    id = 0;
    cf = 0;

    prog_mode();
    for ( i=0; i<PSIZE; ++i ) {
	command(RDPROG);
	if ( (progbuf[i] = in_word() & 0x3FFF) != 0x3FFF )
	    pmlast = i;
	command(INCADD);
    }
    prog_mode();
    for ( i=0; i<DSIZE; ++i ) {
	command(RDDATA);
	if ( (databuf[i] = in_word() & 0xFF) != 0xFF )
	    dmlast = i;
	command(INCADD);
    }
    prog_mode();
    command(LDCONF);
    out_word(0x3FFF);
    for ( i=0; i<4; ++i ) {
	command(RDPROG);
	if ( (idbuf[i] = in_word() & 0x3FFF) != 0x3FFF )
	    id = IBASE;
	command(INCADD);
    }
    for ( i=0; i<3; ++i )
	command(INCADD);
    command(RDPROG);
    if ( (config = in_word() & 0x1F) != 0x1F )
	cf = CBASE;

    return !(pmlast == -1 && dmlast == -1 && id == 0 && cf == 0 );
}


char *conf_str(U16 cfg)
{
    static char s[5];

    s[0] = (cfg&CP)? '-': 'C';
    s[1] = (cfg&PWRTE)? 'P': '-';
    s[2] = (cfg&WDTE)? 'W': '-';
    s[3] = "LXHR"[cfg&3];
    s[4] = '\0';

    return s;
}


void main(int argc, char *argv[])
{
   FILE *fp;
   int i, c, e, got_file = 0, t;
   char *fn;
   U16 temp;
   time_t start_t;

   erasehex(PSIZE, DSIZE, 14);          /* initialise buffers */
   config = RC|CP;                      /* default config */

   for ( i=1; i<argc; ++i ) {
      if ( (c = *argv[i]) == '-' || c == '/' )
	 get_option(++argv[i]);
      else {
	 if ( got_file )
	     usage();
	 fn = argv[i];
	 got_file = 1;
      }
   }

   if ( (e = setup()) != 0 )
       if ( e == -1 )
	   quit("Illegal LPT port (check PPLPT setting)");
       else
	   quit("Hardware fault");

   valid = 1;

   if ( !erase && !got_file )              /* no file needed for erase */
       usage();

   if ( dump && !got_file )                /* need file for dump */
       usage();

   if ( verify_only && !got_file )         /* need file for verify */
       usage();

   printf("%s  %s  %s\n\n",does,version,copyright);

   if ( wait ) {
      printf("Insert PIC and press a key to start (^C to abort) ...\n\n");
      if ( getch() == 3 )
	 quit("Aborted");
   }

    if ( dump ) {
       if ( read_pic() ) {
	   if ( (fp = fopen(fn,"w")) == NULL )
	       quit("Can't create hexfile");
	   format = dumpfmt;
	   dumphex(fp, pmlast+1, dmlast+1, IBASE, CBASE, DBASE);
	   fclose(fp);
	   quit(NULL);
       }
       quit("PIC is erased - nothing to dump");
    }

    if ( got_file ) {
	if ( (fp = fopen(fn,"r")) == NULL )
	    quit("Can't open hexfile");
	temp = config;
	if ( (e = loadhex(fp, PSIZE, DSIZE, IBASE, CBASE, DBASE)) < 0 )
	    quit(errhex(e));
	if ( setcfg )           /* use command-line config if set */
	    config = temp;
	else if ( cf > 0 )
	    ++setcfg;
    }

    start_t = time(NULL);
    if ( verify_only ) {
	printf("Verifying ...\n");
	if ( pmlast >= 0 )
	    verify(progbuf,pmlast+1,0x3FFF,RDPROG,0);
	if ( dmlast >= 0 )
	    verify(databuf,dmlast-DBASE+1,0xFF,RDDATA,DBASE);
	if ( id > 0 )
	    verify(idbuf,4,0x3FFF,RDPROG,IBASE);
       verify(&config,1,0x1F,RDPROG,CBASE);
   } else {
       if ( erase ) {
	   printf("Erasing ...\n");
	   erase_all();
	   if ( !got_file )
	       quit(NULL);
       }
       printf("Programming ...\n");
       if ( pmlast >= 0 )
	   program(progbuf,pmlast+1,0x3FFF,LDPROG,RDPROG,0);
       if ( dmlast >= 0 )
	   program(databuf,dmlast-DBASE+1,0xFF,LDDATA,RDDATA,DBASE);
       if ( id > 0 )
	   program(idbuf,4,0x3FFF,LDPROG,RDPROG,IBASE);
       printf("Setting config to %s ... %s\n",
	       conf_str(config), setcfg? "": "(Warning: using default!)");
       program(&config,1,0x1F,LDPROG,RDPROG,CBASE);
   }
   t = (int) (time(NULL)-start_t);
   printf("Finished in %d sec%c\n\n",t,(t!=1)? 's': ' ');
   idle_mode();
}
