/*
 * An adaption of EPROMmer.bas
 * By Udi Finkelstein (C) 1990
 */

#include <stdio.h>
#include <stdarg.h>
#include "req/reqbase.h"
#include "req/req_protos.h"
#include "req/req.h"

/*
 * So PowerWindows doesn't add a 'chip' keyword?? so what ...
 */
#define USHORTTMP USHORT
#undef USHORT
#define USHORT USHORTTMP chip
#include "EPROMmer.h"
#undef USHORT
#define USHORT USHORTTMP

/*
 * Since I have blown PA0 on my machine, I have used one of the RS232
 * signals instead, and used a 1489 receiver on the circuit instead of
 * using PA0.
 */
#ifdef USE_RS232
#define OE 0x41
#else
#define OE 0x01
#endif

#define PGM 0x02

#define VOLTS12 (223 - 128)
#define VOLTS21 (223 - 64)
#define VOLTS25 (223 - 192)

/* PA2 = SEL  */
#define Clock0 0
#define Clock1 4

unsigned char far EmemBase[0x10000];
char *EmemTop,*RamBot;
volatile unsigned char *ParPort = (volatile unsigned char *)0xbfe101;
volatile unsigned char *ParDDR = (volatile unsigned char *)0xbfe301;
volatile unsigned char *CtrlPort = (volatile unsigned char *)0xbfd000;
volatile unsigned char *CtrlDDR = (volatile unsigned char *)0xbfd200;
char *EpromType;

unsigned char volt_tbl[] = { VOLTS25, VOLTS25, VOLTS21, VOLTS21,
	VOLTS12, VOLTS12, VOLTS12, VOLTS12, VOLTS12 };

/* PA0 = /OE, PA1 = /PGM */
unsigned char rp_tbl[] = { 0, 0, 0, PGM, PGM, PGM, 0, 0, 0 };
unsigned char wp_tbl[] = { OE+PGM, OE, OE, OE, OE, OE, OE, OE, OE };
unsigned char lp_tbl[] = { OE+PGM, OE+PGM, OE+PGM, OE+PGM, OE+PGM,
	OE+PGM, OE+PGM, OE+PGM, OE+PGM };
unsigned char ip_tbl[] = { OE, OE+PGM, OE+PGM, OE+PGM, OE+PGM, OE+PGM,
	OE+PGM, OE+PGM, OE+PGM };
unsigned char e_flag[] = { 1, 1, 1, 1, 1, 1, 1, 1, 0 };

unsigned short ad_tbl[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0x8000 };

short Wr50ms(unsigned char), WrIntel(unsigned char), WrFast(unsigned char),
	WrEprommer(unsigned char);

struct Gadget *ag_tbl[] = { &FiftyMSGad, &FiftyMSGad, &FiftyMSGad,
	&IntelGad, &IntelGad, &IntelGad, &IntelGad, &IntelGad, &FastGad };
short (*wa_tbl2[])(unsigned char) = { Wr50ms, WrIntel, WrEprommer, WrFast };

short (*WriteAlg)(unsigned char);

long size_tbl[] = { 2048, 4096, 4096, 8192, 8192, 16384, 32768, 65536, 32768 };

unsigned char VoltVal, ReadParam, WriteParam, LeaveParam, InhibitParam;

unsigned char Addr0, Addr1, Addr2, Addr3;
unsigned char Write1, Write2, Write3, Write4, Write5;
unsigned char Read0, Read1;

unsigned short LastAddr, AdParam;

LONG *RAMValInt, *RAMLowInt, *EPROMLowInt, *LengthInt;

struct Window *win;
struct RastPort *rp;
struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;
struct Library *ReqBase;
struct Gadget *ia, *old_type_gad, *old_alg_gad;
struct IntuiMessage *m;
struct MsgPort *tp;
struct timerequest *tr;
ULONG mc;
long Size;
short EPType = -1;

char Dir[DSIZE + 1], File[FCHARS + 1], FullPath[DSIZE+FCHARS+2];
struct ReqFileRequester frq ;

void UpdateLongInt(g)
struct Gadget *g;
{
SHORT pos;
struct StringInfo *si;

	si = (struct StringInfo *)(g->SpecialInfo);
	pos = RemoveGList(win, g, 1);
	sprintf(si->Buffer, "%lu", si->LongInt);
	si->NumChars = strlen(si->Buffer);
	AddGList(win, g, pos, 1, NULL);
	RefreshGList(g, win, NULL, 1);
}

/*
 * Turn off the previously selected gadget, if there was one.
 */
void GadExclude(gad)
struct Gadget *gad;
{
	if (gad) {
/* Turn off previous gadget */
		gad->Flags ^= SELECTED;
		SetAPen(rp, 0);
		SetDrMd(rp, JAM1);
		RectFill(rp, gad->LeftEdge, gad->TopEdge,
			gad->LeftEdge + gad->Width, gad->TopEdge + gad->Height);
		RefreshGList(gad, win, NULL, 1);
	}
}

/*
 * A general one gadget requester using req.library
 */
void SimpleRequest(char *str,...)
{
va_list ap;
struct TRStructure trs;

	va_start(ap,str);

	trs.Text = str;
	trs.Controls = ap;
	trs.Window = 0;
	trs.MiddleText = 0;
	trs.PositiveText = 0;
	trs.NegativeText = "Resume";
	trs.Title = "Ahem...";
	trs.KeyMask = 0xFFFF;
	trs.textcolor = 1;
	trs.detailcolor = 2;
	trs.blockcolor = 3;
	trs.versionnumber = REQVERSION;
	trs.Timeout = 0;
	trs.AbortMask = 0;
	trs.rfu1 = 0;

	TextRequest(&trs);
	va_end(ap);
}

/*
 * A general two gadget requester using req.library
 */
short TwoGadRequest(char *str,...)
{
va_list ap;
struct TRStructure trs;
short res;

	va_start(ap,str);

	trs.Text = str;
	trs.Controls = ap;
	trs.Window = 0;
	trs.MiddleText = 0;
	trs.PositiveText = "  OK  ";
	trs.NegativeText = "CANCEL";
	trs.Title = "Ahem...";
	trs.KeyMask = 0xFFFF;
	trs.textcolor = 1;
	trs.detailcolor = 2;
	trs.blockcolor = 3;
	trs.versionnumber = REQVERSION;
	trs.Timeout = 0;
	trs.AbortMask = 0;
	trs.rfu1 = 0;

	res = TextRequest(&trs);
	va_end(ap);
	return(res);
}

/*
 * Timer initialization done here
 */
short OpenTimer()
{

	if ((tp = CreatePort(0, 0)) == 0) return 1;
	if ((tr = (struct timerequest *)CreateExtIO(tp, sizeof(struct timerequest)))
		== NULL) return 1;

	if (OpenDevice("timer.device", UNIT_MICROHZ, (struct IORequest *)tr, 0))
		return 1;
}

/*
 * Timer cleanup done here
 */
void CloseTimer(tr)
struct timerequest *tr;
{

	if (tp) DeletePort(tp);
	if (tr) {
		CloseDevice((struct IORequest *)tr);
		DeleteExtIO((struct IORequest *)tr);
	}
}

/*
 * A MicroHz precision delay routine
 */
void uDelay(delay)
unsigned long delay;
{

	tr->tr_node.io_Command = TR_ADDREQUEST;
	tr->tr_time.tv_secs = 0;
	tr->tr_time.tv_micro = delay;
	DoIO((struct IORequest *)tr);
}

void SetAddr(Address)
unsigned short Address;
{

	LastAddr = Address;
	*ParDDR = 255;
	*ParPort = 253;
	*CtrlPort = Addr0;
	*ParPort = Address >> 8;
	*CtrlPort = Addr1;
	*ParPort = 254;
	*CtrlPort = Addr2;
	*ParPort = Address & 255;
	*CtrlPort = Addr3;
}

void InitPorts()
{

	*CtrlDDR = Clock1 + OE + PGM;
	*CtrlPort = Clock0 + OE + PGM;
	*ParDDR = 255;
	*ParPort = 255;
	*CtrlPort = Clock1 + OE + PGM;
	*CtrlPort = Clock0 + OE + PGM;;

	SetAddr(0);
}

void VPP_on()
{
	*ParDDR = 255;
	*ParPort = VoltVal;
	*CtrlPort = Clock1;
}

void VPP_off()
{
	*ParPort = 255;
	*CtrlPort = LeaveParam + Clock0;
	*CtrlPort = LeaveParam + Clock1;
	*CtrlPort = LeaveParam + Clock0;
}

void ThatsIt()
{
	SetAddr(0);
	*CtrlDDR = 0;
	*ParDDR = 0;
}

unsigned char ReadByte()
{
unsigned char DataIn;

/* If A15 is used for /WR, make sure it's high now - used for 62256 */
	if (AdParam & 0x8000 & ~LastAddr) SetAddr(LastAddr | 0x8000);
/* port is now input */
	*ParDDR = 0;
	*CtrlPort = Read0;
	DataIn = *ParPort;
	*CtrlPort = Read1;
	return DataIn;
}

void WriteByte(DataOut, delay)
unsigned char DataOut;
unsigned long delay;
{
/* If A15 is used for /WR, make sure it's low now - used for 62256 */
	if (AdParam & 0x8000 & LastAddr) SetAddr(LastAddr & 0x7fff);
	*ParDDR = 255;
	*ParPort = VoltVal;
	*CtrlPort = Write1;
	*ParPort = DataOut;
	*CtrlPort = Write2;
	uDelay(delay);
	*CtrlPort = Write3;
	*ParPort = 255;
	*CtrlPort = Write4;
	*CtrlPort = Write5;
}

void SetCycleParams()
{
	Write1 = InhibitParam + Clock1;
	Write2 = WriteParam + Clock1;
	Write3 = InhibitParam + Clock0;
	Write4 = InhibitParam + Clock1;
	Write5 = LeaveParam + Clock0;
	Read0 = ReadParam + Clock0;
	Read1 = LeaveParam + Clock0;
	Addr0 = LeaveParam + Clock1;
	Addr1 = LeaveParam + Clock0;
	Addr2 = LeaveParam + Clock1;
	Addr3 = LeaveParam + Clock0;
}

/*
 * Write a single byte
 */
short WriteCycle(DataOut)
unsigned char DataOut;
{
unsigned char c;

/* Any bit set in DataOut which is already reset in the EPROM? */
	c = ReadByte();
	if (e_flag[EPType] && (~c & DataOut)) return 1;

/* Is data already identical? */
	if (DataOut == c) return 0;

	return (*WriteAlg)(DataOut);
}

/*
 * The Eprommer algorythm - a variation on the intel algorythm.
 */
short WrEprommer(DataOut)
unsigned char DataOut;
{
short n;
unsigned long l;

/* Programming loop */
	n = 0;
	while(++n < 20) {
		WriteByte(DataOut, 1000);
		if (ReadByte() == DataOut) {
/* Overprogramming section */
			l = n * 5000;
			if (l > 20000) l = 20000;
			WriteByte(DataOut, l);
			return 0;
		}
	}
/* Too many retries, must be EPROM failure */
	return 2;
}

/*
 * The IntEligent programming algorythm algorythm.
 */
short WrIntel(DataOut)
unsigned char DataOut;
{
short n;

/* Programming loop */
	n = 0;
	while(++n < 15) {
		WriteByte(DataOut, 1000);
		if (ReadByte() == DataOut) {
/* Overprogramming section */
			WriteByte(DataOut, n * 4000);
			return 0;
		}
	}
/* Too many retries, must be EPROM failure */
	return 2;
}

/*
 * The fastest algorythm - use for RAMs only!
 */
short WrFast(DataOut)
unsigned char DataOut;
{
/* No loop! */
	WriteByte(DataOut, 10);
	if (ReadByte() == DataOut) return 0;

/* RAM failure */
	return 2;
}

/*
 * The basic 50ms pulse algorythm.
 * Use for 2716, 2732 and 2732A chips which requires it. not all
 * brands of chips require this slow algorythm (Intel does, TI don't).
 */
short Wr50ms(DataOut)
unsigned char DataOut;
{
/* No loop! */
	WriteByte(DataOut, 50000);
	if (ReadByte() == DataOut) return 0;

/* RAM failure */
	return 2;
}

/*
 * Read a file into the buffer
 */
void LoadFile()
{
FILE *fin;

	frq.VersionNumber = REQVERSION;
	frq.Dir = Dir;
	frq.File = File;
	frq.PathName = FullPath;
	frq.Title = "Buffer Load";
	frq.Flags = FRQCACHINGM|FRQLOADINGM|FRQHIDEWILDSM;

	if (!FileRequester(&frq)) return;

	if ((fin = fopen(FullPath, "r")) == NULL) {
		SimpleRequest("Could not open input file (%s)!", FullPath);
		return;
	}
	*LengthInt = fread(EmemBase + *RAMLowInt, 1, 0x10000 - *RAMLowInt, fin);
	SimpleRequest("We read %ld bytes", *LengthInt);
	UpdateLongInt(&Length);
	fclose(fin);
}

/*
 * Save a part of the buffer to disk
 */
void SaveFile()
{
FILE *fin;
LONG len;

	frq.VersionNumber = REQVERSION;
	frq.Dir = Dir;
	frq.File = File;
	frq.PathName = FullPath;
	frq.Title = "Buffer Save";
	frq.Flags = FRQCACHINGM|FRQLOADINGM|FRQHIDEWILDSM;

	if (!FileRequester(&frq)) return;

	if ((fin = fopen(FullPath, "w")) == NULL) {
		SimpleRequest("Could not open output file!");
		return;
	}
	len = fwrite(EmemBase + *RAMLowInt, 1, *LengthInt, fin);
	if (len == *LengthInt)
		SimpleRequest("We wrote %ld bytes", len);
	else
		SimpleRequest("We wrote only %ld bytes Instead of %ld bytes",
			len, *LengthInt);
	fclose(fin);
}

/*
 * Read the EPROM
 */
void ReadEPROM()
{
unsigned short addr, len;
unsigned char *c;

	if (EPType == -1) {
		SimpleRequest("You must select an EPROM type first!");
		return;
	}
	len = *LengthInt;
	addr = *EPROMLowInt;
	c = &EmemBase[*RAMLowInt];
	while (len--) {
		SetAddr(addr++);
		*c++ = ReadByte();
	}
	SimpleRequest("We read %ld bytes", *LengthInt);
}

/*
 * Compare the EPROM with the RAM buffer
 */
void CompareEPROM()
{
unsigned short addr, len;
unsigned char *c, ce, cr;

	if (EPType == -1) {
		SimpleRequest("You must select an EPROM type first!");
		return;
	}
	len = *LengthInt;
	addr = *EPROMLowInt;
	c = &EmemBase[*RAMLowInt];
	while (len--) {
		SetAddr(addr++);
		if ((cr = *c++) != (ce = ReadByte()))
			if (!TwoGadRequest("Offset %ld, EPROM:%02lx   RAM:%02lx",
				addr - *EPROMLowInt - 1, ce, cr)) return;
	}
	SimpleRequest("We compared %ld bytes", *LengthInt);
}

/*
 * Make sure EPROM is empty
 */
void CheckEPROM()
{
unsigned long addr;

	if (EPType == -1) {
		SimpleRequest("You must select an EPROM type first!");
		return;
	}
	for (addr = 0; addr < Size;) {
		SetAddr((unsigned short)(addr++));
		if (ReadByte() != 255) {
			SimpleRequest("EPROM contains data at %ld!", addr - 1);
			return;
		}
	}
	SimpleRequest("EPROM is empty");
}

/*
 * Program the EPROM
 */
void ProgramEPROM()
{
unsigned short addr, len;
unsigned char *c;

	if (EPType == -1) {
		SimpleRequest("You must select an EPROM type first!");
		return;
	}
	len = *LengthInt;
	addr = *EPROMLowInt;
	c = &EmemBase[*RAMLowInt];
	while (len--) {
		SetAddr(addr++);
		switch (WriteCycle(*c++)) {
			case 2:
				SimpleRequest("Verify error at EPROM address %d", addr - 1);
				return;

			case 1:
				SimpleRequest("EPROM already has data at address %d", addr - 1);
				return;
		}
	}
	SimpleRequest("We programmed %ld bytes", *LengthInt);
}

/*
 * free every resource we might have allocated
 */
void cleanup(s)
char *s;
{
	ThatsIt();
	PurgeFiles(&frq);
	if (s) Write(Output(), s, strlen(s));
	if (tp) CloseTimer(tr);
	if (win) CloseWindow(win);
	if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
	if (GfxBase) CloseLibrary((struct Library *)GfxBase);
	if (ReqBase) CloseLibrary(ReqBase);
	exit(0);
}

main()
{

/*
 * Open all libraries, as well as the program's window
 */
	if (!(IntuitionBase = (struct IntuitionBase *)
		OpenLibrary("intuition.library", 33L)))
			cleanup("No intuition.library!\n");

	if (!(GfxBase = (struct GfxBase *) OpenLibrary("graphics.library", 33L)))
		cleanup("No graphics.library!\n");

	if (!(ReqBase = OpenLibrary("req.library", 0L)))
		cleanup("No req.library!\n");

	if (!(win = OpenWindow(&NewWindowStructure1)))
			cleanup("No window!\n");

	rp = win->RPort;

/* Initialization for timer */
	if (OpenTimer()) cleanup("No timer!");

/*
 * Warning! due to a Lattice bug, we can't initialize these variables while
 * they're defined, even though it's value is known at the time. They get
 * initialized, but with the wrong values. Instead, we initialize them at
 * run time.
 */
	RAMLowInt = &(((struct StringInfo *)(RAMLow.SpecialInfo))->LongInt);
	RAMValInt = &(((struct StringInfo *)(RAMVal.SpecialInfo))->LongInt);
	EPROMLowInt = &(((struct StringInfo *)(EPROMLow.SpecialInfo))->LongInt);
	LengthInt = &(((struct StringInfo *)(Length.SpecialInfo))->LongInt);

/* Update display */
	UpdateLongInt(&RAMLow);
	UpdateLongInt(&Length);
	UpdateLongInt(&EPROMLow);

/* Initialize hardware */
	InitPorts();

/*
 * Event loop
 */
	while (1) {
		Wait(1L << (win->UserPort->mp_SigBit));
		while (m = (struct IntuiMessage *)GetMsg(win->UserPort)) {
			ia = (struct Gadget *)m->IAddress;
			mc = m->Class;
			ReplyMsg((struct Message *)m);
			if (mc == CLOSEWINDOW) {
				SimpleRequest("Please remove the EPROM now");
				cleanup(NULL);
			} else if (mc == GADGETUP) {
				if (ia == &LoadGad) LoadFile();
				else if (ia == &SaveGad) SaveFile();
				else if (ia == &ReadGad) ReadEPROM();
				else if (ia == &CheckGad) CheckEPROM();
				else if (ia == &ProgramGad) ProgramEPROM();
				else if (ia == &CompareGad) CompareEPROM();
				else if (ia == &VPPGad) {
					VPP_on();
					SimpleRequest("VPP on now, press OK to turn it off");
					VPP_off();
				} else if (ia == &ReadByteGad) {
					*RAMValInt = EmemBase[*RAMLowInt];
					UpdateLongInt(&RAMVal);
				} else if (ia == &WriteByteGad) {
					EmemBase[*RAMLowInt] = (*RAMValInt &= 255);
					UpdateLongInt(&RAMVal);
				}
			} else if (mc == GADGETDOWN) {
				if ((LONG)(ia->UserData) & 0x200) {
/* get EPROM type */
					EPType = (unsigned char)(ia->UserData);
/* set EPROM parameters */
					Size = size_tbl[EPType];
					VoltVal = volt_tbl[EPType];
					ReadParam = rp_tbl[EPType];
					WriteParam = wp_tbl[EPType];
					InhibitParam = ip_tbl[EPType];
					AdParam = ad_tbl[EPType];
					*CtrlPort = LeaveParam = lp_tbl[EPType];
					SetCycleParams();
/* turn off old gad */
					GadExclude(old_type_gad);
					old_type_gad = ia;
/* inform user */
					SimpleRequest("Check for correct Personality Module");
/* fake a gadget event to set the defalut algorythm for the new type */
					ia = ag_tbl[EPType];
/* Use GadExclude to turn the new gadget ON! */
					GadExclude(ia);
/* Fall into the algorythm gadget's code to turn old gad off */
				}
				if ((LONG)(ia->UserData) & 0x100) {
					WriteAlg = wa_tbl2[(unsigned char)(ia->UserData)];
					GadExclude(old_alg_gad);
					old_alg_gad = ia;
				}
 			}
		}
	}
}
