#include <unistd.h>
#include <sys/io.h>
#include <stdio.h>
#include <time.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define IOBASE 0x378

char controlRegister;
void updateControlRegister() {
	int i;

	/* ensure shift register and output latch clocks are low */
	outb((inb(IOBASE+2) | 0x1), IOBASE+2); // C0 is inverted
	outb((inb(IOBASE+2) | 0x2), IOBASE+2); // C1 is inverted

	for(i = 7; i > -1; i--) {
		/* data bit */
		outb((inb(IOBASE+2) & ~0x4) | (((controlRegister >> i) & 0x1)?(0x4):(0x0)), IOBASE+2);

		/* clock */
		outb((inb(IOBASE+2) & ~0x1), IOBASE+2);
		outb((inb(IOBASE+2) | 0x1), IOBASE+2);
	}

	/* latch parallel output */
	outb((inb(IOBASE+2) & ~0x2), IOBASE+2);
	outb((inb(IOBASE+2) | 0x2), IOBASE+2);
}

void setRST(int volts) {
	switch(volts) {
		case 0:
			controlRegister |= 0x40;
			updateControlRegister();
			break;
		case 5:
			controlRegister &= ~0x40;
			controlRegister |= 0x20;
			updateControlRegister();
			break;
		case 12:
			controlRegister &= ~0x60;
			updateControlRegister();
			break;
		default:
			fprintf(stderr, "setRST%d): undefined voltage\n", volts);
			exit(1);
	}
}

void setVCC(int volts) {
	switch(volts) {
		case 0:
			controlRegister |= 0x80;
			updateControlRegister();
			break;
		case 5:
			controlRegister &= ~0x80;
			updateControlRegister();
			break;
		default:
			fprintf(stderr, "setVCC(%d): undefined voltage\n", volts);
			exit(1);
	}
}

void setMode(char nibble) {
	controlRegister &= ~0x1E;
	controlRegister |= (nibble & 0x0F) << 1;
	updateControlRegister();
}

void setPROG(char bit) {
	controlRegister &= ~0x01;
	controlRegister |= (bit & 0x01);
	updateControlRegister();
}

void setXTAL1(char bit) {
	outb((inb(IOBASE+2) & ~0x8) | ((bit & 0x1)?(0x0):(0x8)), IOBASE+2);
}

void setData(char byte) {
	outb(byte, IOBASE);
	outb(inb(IOBASE+2) & ~0x20, IOBASE+2);
}

char readData() {
	outb(inb(IOBASE+2) | 0x20, IOBASE+2);
	return inb(IOBASE);
}

char readBusy() {
	return ((inb(IOBASE+1) & 0x8)?(1):(0));
}

void init() {
	if(ioperm(IOBASE, 3, 1)) {
		perror("ioperm(1)");
		exit(-1);
	}

	setRST(0);
	setVCC(0);
	setPROG(0);
	setMode(0);
	setXTAL1(0);
	readData(); // tri-state databyte
}

void cleanup() {
	setMode(0);
	setPROG(0);
	setXTAL1(0);
	setRST(0);
	setVCC(0);

	if(ioperm(IOBASE, 3, 0)) {
		perror("ioperm(0)");
		exit(-1);
	}
}

void readFlash(char *buf, int len) {
	int i;

	setVCC(5);
	setRST(0);
	setXTAL1(0);
	setPROG(1);
	setMode(0xC);
	usleep(1000);
	setRST(5);
	usleep(1000);

	for(i = 0; i < len; i++) {
		buf[i] = readData();
		setXTAL1(1);
		setXTAL1(0);
	}

	setMode(0);
	setPROG(0);
	setRST(0);
	setVCC(0);
}

void readSignature(char *buf, int len) {
	int i;

	setVCC(5);
	setRST(0);
	setXTAL1(0);
	setPROG(1);
	setMode(0x0);
	usleep(1000);
	setRST(5);
	usleep(1000);


	for(i = 0; i < len; i++) {
		buf[i] = readData();
		setXTAL1(1);
		setXTAL1(0);
	}

	setMode(0);
	setPROG(0);
	setRST(0);
	setVCC(0);
}

int chipSize() {
	char buf[2];

	readSignature(buf, 2);
	if(buf[1] == 0x21) return 0x800;	// 89C2051
	if(buf[1] == 0x41) return 0xF00;	// 89C4051

	fprintf(stderr, "unknown chip, signature bytes 0x%.2X%.2X\n", buf[0] & 0xFF, buf[1] & 0xFF);
	return 0;
}

void eraseChip() {
	setVCC(5);
	setRST(0);
	setXTAL1(0);
	setPROG(1);
	setMode(0x1);
	usleep(1000);
	setRST(5);
	usleep(1000);

	/* pull PROG low for 10ms to erase */
	setRST(12);
	usleep(1000);
	setPROG(0);
	usleep(10000);
	setPROG(1);

	sleep(1);

	setMode(0);
	setPROG(0);
	setRST(0);
	setVCC(0);
}

void writeFlash(char *buf, int len) {
	int i, j;

	setVCC(5);
	setXTAL1(0);
	setRST(0);
	setPROG(1);
	setMode(0xE);
	usleep(1000);
	setRST(5);
	usleep(1000);
	setRST(12);
	usleep(100000);

	for(i = 0; i < len; i++) {
		setData(buf[i]);
		setPROG(0);
		setPROG(1);
		for(j = 0; j < 10000; j++) if(readBusy()) break;
		if(j == 10000) {
			fprintf(stderr, "%d loops waiting for end of program?\n", j);
			break;
		}
		if(i == len - 1) setRST(5);	// no clobber next byte
						// I don't know why this is needed
						// unless PROG is getting glitched
//		printf("waited %d\n", j);
		setXTAL1(1);
		setXTAL1(0);
	}

	setMode(0);
	setPROG(0);
	setRST(0);
	setVCC(0);
}

void hexdump(char *buf, int len, int offset) {
	int i, j;

	for(i = 0; i < len; i+= 16) {
		printf("0x%.4X: ", i + offset);
		for(j = 0; (j < 16) && (i+j < len); j++)
        		printf("%.2X ", buf[i+j] & 0xFF);
		for( ; j < 16; j++)
        		printf("   ");
		printf("    ");
		for(j = 0; (j < 16) && (i+j < len); j++)
        		if(isprint(buf[i+j]))
                		printf("%c", buf[i+j]);
        		else
                		printf(".");
		printf("\n");
	}
}

void usage() {
	fprintf(stderr, "\n89Cx051 Programmer Driver\n");
	fprintf(stderr, "(c) 2003 Alan Yates <alany@ay.com.au>\n");
	fprintf(stderr, "http://www.vk2zay.net/\n");
	fprintf(stderr, "$Id: 89cx051.c,v 1.4 2003/05/08 03:55:07 alany Exp $\n\n");
	fprintf(stderr, "Commands:\n");
	fprintf(stderr, "\tq\t\tquit\n");
	fprintf(stderr, "\t?\t\tthis usage message\n");
	fprintf(stderr, "\te\t\terase chip\n");
	fprintf(stderr, "\ts\t\tdump signature block for inspection\n");
	fprintf(stderr, "\td\t\tdump flash as hex for inspection\n");
	fprintf(stderr, "\tr <file>\tdownload flash into local file\n");
	fprintf(stderr, "\tw <file>\tupload local file into flash\n");
}

void downloadToFile(char *filename, int size) {
	int fp, ret;
	char buf[0xF00];

	readFlash(buf, size);	
	if((fp = open(filename, O_WRONLY | O_CREAT)) < 0) {
		perror("open()");
		return;
	}
	ret = write(fp, buf, size);
	if(ret != size) {
		perror("write()");
		return;
	}
	close(fp);
	fprintf(stderr, "copied %d bytes\n", ret);
}

void uploadFromFile(char *filename, int size) {
	int i, fp, ret;
	char buf[0xF00], buf2[0xF00];

	if((fp = open(filename, O_RDONLY)) < 0) {
		perror("open()");
		return;
	}
	ret = read(fp, buf, size);
	if(ret < 1) {
		perror("read()");
		return;
	}
	close(fp);

	writeFlash(buf, ret);
	fprintf(stderr, "programmed %d bytes\n", ret);
	readFlash(buf2, ret);
	for(i = 0; i < ret; i++) {
		if(buf[i] != buf2[i]) {
			fprintf(stderr, "verify failed at 0x%.3X: expected 0x%.2X, got 0x%.2X\n", i, buf[i] & 0xFF, buf2[i] & 0xFF);
			break;
		}
	}
}

int main(int argc, char **argv) {
	int len, run = -1;
	char c, buf[0xF00];

	init();

	fprintf(stderr, "89Cx051> ");
	while(run) {
		if(scanf("%c", &c) < 1) break;
		switch(c) {
			case 's':
			case 'S':
				readSignature(buf, 0x40);
				hexdump(buf, 0x40, 0);
				break;
			case 'e':
			case 'E':
				;
				if((len = chipSize())) eraseChip();
				break;
			case 'd':
			case 'D':
				if((len = chipSize())) {			
					readFlash(buf, len);
					hexdump(buf, len, 0);
				}
				break;
			case 'r':
			case 'R':
				scanf("%s", buf);
				if((len = chipSize())) downloadToFile(buf, len);
				break;
			case 'w':
			case 'W':
				scanf("%s", buf);
				if((len = chipSize())) uploadFromFile(buf, len);
				break;
			case 'q':
				run = 0;
				continue;
/*			case 'c':
				scanf("%i", &len);
				controlRegister = len & 0xFF;
				updateControlRegister();
				break; */
			case 'v':
				scanf("%i", &len);
				setRST(len);
				break;
			case '\n':
				break;
			case 'h':
			case '?':
			default:
				usage();
				break;
		}
		if(c != '\n') fprintf(stderr, "89Cx051> ");
	}
	fprintf(stderr, "\n");

	cleanup();
	return 0;
}
