/*
#
# This is code to run a home-made EPROM programmer
#
# $Id: 27c801.c,v 1.20 2002/09/05 00:38:43 nemesis Exp $
#
# Copyright (C) 2002 Kees Cook
# kees@outflux.net, http://outflux.net/
# 
# 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, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
# http://www.gnu.org/copyleft/gpl.html
#
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>   // memset
#include <sys/time.h> // gettimeofday

// scheduler prioriety
#include <sched.h>
#include <sys/io.h>

// stat
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

// global variables
unsigned int par_port=0x378;
unsigned char CTRL=0;
unsigned char DATA=0;


// configure the parallel port we're going to use
#define PARALLEL_PORT	(par_port)
#define DATA_PORT	(PARALLEL_PORT)
#define STATUS_PORT	(PARALLEL_PORT+1)
#define CTRL_PORT	(PARALLEL_PORT+2)

#define	C0		(1<<0)
#define C1		(1<<1)
#define C2		(1<<2)
#define C3		(1<<3)
#define DIR_READ	(1<<5)

#define SET_BITS(value)	{ CTRL |=  (value); outb(CTRL,CTRL_PORT); }
#define CLR_BITS(value) { CTRL &= ~(value); outb(CTRL,CTRL_PORT); }	

#define START_READ	SET_BITS(DIR_READ);
#define START_WRITE	CLR_BITS(DIR_READ);

// to pulse enable, we want to bring the LINE from high to low, hold, then
// low to high
// /C1: high is 0.  low is 1.
#define ENABLE_DEFAULT	{ CLR_BITS(C1); }
#define ENABLE_ON	{ SET_BITS(C1); }
#define ENABLE_OFF	{ CLR_BITS(C1); }
#define ENABLE_PULSE	{ SET_BITS(C1); CLR_BITS(C1); }

// to pulse reset, we want to bring the LINE from low to high, hold, then
// high to low
// C2: high is 1, low is 0
#define RESET_DEFAULT	{ CLR_BITS(C2); }
#define RESET_PULSE	{ SET_BITS(C2); CLR_BITS(C2); }

// to pulse the address clock, we want to bring the LINE from low to high,
// hold, then high to low
// /C0: high is 0, low is 1
#define ADDR_DEFAULT	{ SET_BITS(C0); }
#define ADDR_PULSE	{ CLR_BITS(C0); SET_BITS(C0); }

// to turn on programming voltage, we want to bring the LINE to low
// /C3: high is 0, low is 1
#define PROGRAM_OFF	{ CLR_BITS(C3); START_READ; }
#define PROGRAM_ON	{ START_WRITE; SET_BITS(C3); }

#define DATA_GET	(inb(DATA_PORT))
#define DATA_PUT(byte)	{ outb(byte,DATA_PORT); }

#define INIT		{	\
	PROGRAM_OFF;	\
	ENABLE_DEFAULT;	\
	ADDR_DEFAULT;	\
	RESET_DEFAULT;	\
	DATA_PUT(0);	\
	RESET_PULSE;	\
}



#define	MAX_ADDRESS	(1048576)

unsigned char byte[MAX_ADDRESS];

void press()
{
	char buf[1024];

	printf("Press enter...");
	fflush(NULL);
	fgets(buf,1024,stdin);
}

void write_from_file(char * filename)
{
	FILE * fp;
	struct stat info;
	int    n, i, k, prevk, verified, reburn;
	size_t bytes;

	// open the file
	if ((fp=fopen(filename,"r"))==NULL)
	{
		perror(filename);
		exit(1);
	}
	// find out how big it is
	if (fstat(fileno(fp),&info)<0)
	{
		perror("fstat");
		exit(1);
	}
	bytes=info.st_size;

	// fill "memory" with 0xFF for smaller images
	memset(byte,0xFF,MAX_ADDRESS);

	// handle too-large files
	if (bytes>MAX_ADDRESS)
	{
		fprintf(stderr,"Image file too large!  Cannot program past %d.\n",
				MAX_ADDRESS);
		fprintf(stderr,"Press enter to continue anyway...\n");
		press();
		bytes=MAX_ADDRESS;
	}
	if (bytes<MAX_ADDRESS)
	{
		fprintf(stderr,"Image file too small!  Filling with 0xFF's through %d.\n",
				MAX_ADDRESS);
	      	fprintf(stderr,"Press enter to continue anyway...\n");
		press();
	}

	// read in only the image file bytes
	if (fread(byte,1,bytes,fp)!=bytes)
	{
		perror("fread");
		exit(1);
	}
	fclose(fp);

	RESET_PULSE;
	PROGRAM_OFF;

	// FIXME:
	// I could probably make this thing much faster
	// by first reading the EPROM, and then finding
	// the bytes that CAN take programming, and writing
	// only those, and repeating that read-all, write-some
	// loop until everything verified.

	reburn=0;
	prevk=0;
	for (i=0;i<MAX_ADDRESS;i++)
	{
		unsigned char readback;

		n=0;
		verified=0;
		while (!verified)
		{

			if (n>0)
				reburn++;

			// address valid
			PROGRAM_ON;
			// Vpp valid
			DATA_PUT(byte[i]);
			// data valid
			DATA_PUT(byte[i]);
			DATA_PUT(byte[i]);
			DATA_PUT(byte[i]); // take 2us (tQVEL)

			k=i>>10;
			if (k!=prevk)
			{
				printf("\r%dK",k);
				fflush(NULL);
				prevk=k;
			}

			ENABLE_ON;
			// CE valid
			usleep(45+n); // pause for 45us first
				      // then work our way up to 55us (tELEH)
			ENABLE_OFF;
			ENABLE_OFF;
			ENABLE_OFF; // take 2us (tEHVPX)

			PROGRAM_OFF;
			PROGRAM_OFF;
			PROGRAM_OFF; // take 2us (tVPLEL)

			usleep(30+n);  // this isn't in the spec, but the read
				     // back always failed.  I suspect this
			             // is needed because the chip voltage
			             // is out of spec (datasheet requires
			             // 6.75V, we're only giving it ~5V)

			ENABLE_ON;
			ENABLE_ON;
			ENABLE_ON; // take >1us (tELQV)

			readback=DATA_GET;

			ENABLE_OFF;

			if (readback==byte[i])
				verified=1;
			n++;

			if (n>20)
			{
				printf("Write verify failed after %d attempts @ %x (%02X should be %02X)\n",
						n,i,readback,byte[i]);
				INIT;
				exit(1);
			}
		}

		ENABLE_OFF; // make SURE
		ADDR_PULSE;
		// address-to-enable time is handled by the data line timings
	}
	PROGRAM_OFF;
	printf("\r%dK\n",MAX_ADDRESS>>10);
	printf("Wrote to EEPROM from %s\n",filename);
	printf("Had to over-program %d byte%s.\n",reburn,reburn==1?"":"s");
}

int read_to_file(char * filename)
{
	FILE * fp=NULL;
	int i, k, prevk;
	int allblank=1;

	if ((filename)  && (fp=fopen(filename,"w"))==NULL)
	{
		perror(filename);
		exit(1);
	}

	// read cycle
	PROGRAM_OFF;
	RESET_PULSE;
	prevk=0;
	for (i=0;i<MAX_ADDRESS;i++)
	{
		k=i>>14;
		if (k!=prevk)
		{
			printf("\r%dK",k<<4);
			fflush(NULL);
			prevk=k;
		}

		ENABLE_ON;
		ENABLE_ON;
		ENABLE_ON;

#undef DOUBLE_CHECK_DATA
#ifdef DOUBLE_CHECK_DATA
		{
			unsigned char check[0xf];
			int j;
			int bad=0;

			check[0]=DATA_GET;
			for (j=1;j<0xF;j++)
			{
				check[j]=DATA_GET;
				if (check[0]!=check[j])
					bad=1;
			}
			if (bad)
			{
				printf("read failure @ %x\n",i);
				for (j=0;j<0xF;j++)
				{
					printf("0x%X: 0x%02X\n",j,check[j]);
				}
				INIT;
				exit(1);
			}
		}
#endif
		byte[i]=DATA_GET;
		if (byte[i]!=0xFF) allblank=0;

		ENABLE_OFF;

		ADDR_PULSE;
	}
	printf("\r%dK\n",MAX_ADDRESS>>10);
	if (fp)
	{
		if (fwrite(byte,sizeof(unsigned char),MAX_ADDRESS,fp)!=MAX_ADDRESS)
		{
			perror("fwrite");
			INIT;
			exit(1);
		}
		printf("Dumped EEPROM to %s\n",filename);
		fclose(fp);
	}

	return allblank;
}

void do_tests()
{
	int i;
	struct timeval before;
	struct timeval now;
	long diff;

	gettimeofday(&before,NULL);
	for (i=0;i<1000;i++)
	{
		usleep(50); // 50us
	}
	gettimeofday(&now,NULL);

	// should have slept for 50,000us, or 50ms
	diff=((now.tv_sec-before.tv_sec)*1000000)+(now.tv_usec-before.tv_usec);

	printf("50000us worth of 50us delays took %luus\n",diff);
	press();

	PROGRAM_ON;
	printf("program on\n");
	press();

	PROGRAM_OFF;
	printf("program off\n");
	press();

	// address test
	RESET_PULSE;
	printf("address all zero\n");
	press();

	//11111111111111111111
	//1048575
	for (i=0;i<MAX_ADDRESS-1;i++)
	{
		ADDR_PULSE;
	}
	printf("address all 1s\n");
	press();

	//10101010101010101010
	//base ten:699050
	RESET_PULSE;
	for (i=0;i<699050;i++)
	{
		ADDR_PULSE;
	}
	printf("address 10101010101010101010\n");
	press();

	//01010101010101010101
	//base ten:349525
	RESET_PULSE;
	for (i=0;i<349525;i++)
	{
		ADDR_PULSE;
	}
	printf("address 01010101010101010101\n");
	press();
}

unsigned char orig=0;
void shutdown()
{
	// go back to safe states
	INIT;

	// reset control states
	outb(orig,CTRL_PORT);
}

void get_realtime()
{
        struct sched_param params;

        // get ourself FIFO status so our sleeps are busy-waited
        if (sched_getparam(0,&params)<0)
        {
                perror("sched_getparam");
                exit(1);
        }
        params.sched_priority=sched_get_priority_max(SCHED_FIFO);
        if (sched_setscheduler(0,SCHED_FIFO,&params)<0)
        {
                perror("sched_setscheduler");
                exit(1);
        }
}

void startup()
{
	// get perms to the ports
	if (ioperm(PARALLEL_PORT,3,1)<0)
	{
		perror("ioperm");
		exit(1);
	}

	// configure the initial values
	orig=CTRL=inb(CTRL_PORT);

	// start up
	INIT;

	// register clean-up method
	atexit(shutdown);

	printf("Turn on power to programmer...\n");
	press();
}

void Usage(char * argv[])
{
	printf("Usage: %s [-hs] [-p 0xBASE] [-t] [-b] [-r FILE] [-w FILE]\n",argv[0]);
	printf("-h        This help\n");
        printf("-s        Disable real-time scheduling\n");
        printf("-p 0xBASE Use IO port BASE for parallel port access\n");
	printf("-t        Run programmer tests\n");
	printf("-b        Check if the EPROM is blank\n");
	printf("-r FILE   Read contents of EPROM and write to FILE\n");
	printf("-w FILE   Program EPROM with the contents of FILE\n");
	exit(1);
}

int main(int argc, char * argv[])
{
        int need_realtime=1;
        int need_startup=0;
        int need_tests=0;
        int need_blankcheck=0;
        char * read_filename=NULL;
        char * write_filename=NULL;
	int c;

	while ((c=getopt(argc,argv,"hsp:tbr:w:"))>-1)
	{
		switch (c)
		{
		case 't':
                        need_startup=1;
                        need_tests=1;
                        break;
		case 'b':
                        need_startup=1;
                        need_blankcheck=1;
                        break;
		case 'r':
                        need_startup=1;
                        read_filename=optarg;
                        break;
		case 'w':
                        need_startup=1;
                        write_filename=optarg;
                        break;
                case 's':
                        need_realtime=0;
                        break;
                case 'p':
                        if (sscanf(optarg,"%x",&par_port)!=1)
                                Usage(argv);
                        printf("Using parallel port base 0x%03X\n",par_port);
                        break;
		default:
			printf("Unknown argument '%c'\n",c);
		case 'h':
			Usage(argv);
		}
	}


        if (need_startup)
        {
                if (need_realtime)
                        get_realtime();
                startup();
        }
        else
        {
	        printf("You gotta tell me what to do...\n");
        	Usage(argv);
        }

        if (need_tests)     do_tests();
	if (need_blankcheck)
	{
		if (read_to_file(NULL))
		{
			printf("EPROM is blank\n");
		}	
		else
		{
			printf("EPROM is NOT blank!\n");
		}
	}
        if (read_filename)  read_to_file(read_filename);
        if (write_filename) write_from_file(write_filename);

        return 1;
}
