/* eepromburn.c
 * eeprom burning software to accompanying the eeprom programmer
 * design at: http://www.miranda.org/~jkominek/hardware/eeprom.html
 *
 * written by jay f kominek <jkominek@miranda.org>
 * public domain, 1999... or 2000... somewhere in there...
 */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <time.h>
#include <getopt.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>

#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/io.h>

#define BASEPORT 0x378

#define DataShiftRegCtl (1<<7)
#define ShiftRegClk     (1<<6)
#define DataSerialOut   (1<<5)
#define ShiftRegReset   (1<<4)
#define EEPROMWE        (1<<3)
#define EEPROMOE        (1<<2)
#define AddrHSerialOut  (1<<1)
#define AddrLSerialOut  (1<<0)

#define DataShiftRegOE  (1<<3)

#define DataSerialIn (1<<6)

#undef CSI 1

#define MYoutb(X,Y) {unsigned char foo=(ShiftRegReset|EEPROMWE|EEPROMOE);foo^=(X);outb(foo,Y);}

#define debugpause(X) {/*printf(X);getchar();*/}

void nsleep(long nsec) {
  struct timespec in, out;
  in.tv_sec = 0;
  in.tv_nsec = nsec;
  nanosleep(&in,&out); 
}

void printbinary(unsigned char byte) {
  int i;
  for(i=7;i>=0;i--) {
    printf("%i",(byte&(1<<i))!=0);
  }
  printf("\n");
}

void writebyte(unsigned char byte, unsigned int address, int single) {
  int i; unsigned char val;
  unsigned char al = address&0xFF, ah = (address&0xFF00)>>8;

  val=ShiftRegReset;
  MYoutb(val,BASEPORT);
  nsleep(100);

  /* Load up the serial-in/parallel-out shift register */
  for(i=7;i>=0;i--) {
    int databit = !!(byte&(1<<i)),
      ahbit = !!(ah&(1<<i)),
      albit = !!(al&(1<<i));

    nsleep(100);

    val =
      (databit?DataSerialOut:0) |
      (ahbit?AddrHSerialOut:0)  |
      (albit?AddrLSerialOut:0);

    MYoutb(val,BASEPORT);
    nsleep(100);
    MYoutb(val|ShiftRegClk,BASEPORT);
    nsleep(100);
    MYoutb(val,BASEPORT);
    nsleep(100);
  }

  MYoutb(0,BASEPORT);

  /* Activate write enable and '299 outputs */
  MYoutb(EEPROMWE,BASEPORT);
  outb(DataShiftRegOE,BASEPORT+2);
  //  printf("'299 outputs set: "); printbinary(byte); getchar();

  MYoutb(0,BASEPORT); /* disable write enable */
  outb(0,BASEPORT+2);

  // sleep, probably for 10ms. we can't usefully sleep for 5ms and still
  // let other things use the cpu. letting other things use the cpu is good.
  if(single)
    usleep(5000);
  // else { do nothing, this is part of a page write. }
}

unsigned char readbyte(unsigned int address) {
  unsigned char byte = 0; int i;
  unsigned char val, al = address&0xFF, ah = (address&0xFF00)>>8;

  val=ShiftRegReset;
  MYoutb(val,BASEPORT);
  nsleep(75);

  MYoutb(0,BASEPORT);

  /* Load up the serial-in/parallel-out shift register for the address */
  for(i=7;i>=0;i--) {
    int ahbit = !!(ah&(1<<i)), albit = !!(al&(1<<i));

    nsleep(100);

    val =
      (ahbit?AddrHSerialOut:0) |
      (albit?AddrLSerialOut:0);

    MYoutb(val,BASEPORT);
    nsleep(100);
    MYoutb(val|ShiftRegClk,BASEPORT);
    nsleep(100);
  }

  outb(0,BASEPORT+2);
  MYoutb(EEPROMOE,BASEPORT);
  //  printf("eeprom data bits are on bus\n"); getchar();
  MYoutb(EEPROMOE|DataShiftRegCtl,BASEPORT);

  nsleep(1000);

  //  printf("look at output bit\n"); getchar();
  for(i=7;i>=0;i--) {
    unsigned char status;
    MYoutb(ShiftRegClk,BASEPORT);
    nsleep(100);
    //    printf("look at output bit %i\n",i); getchar();
    MYoutb(0,BASEPORT);
    nsleep(100);
    /* Guarantee that the output has stabilized */
    status = inb(BASEPORT+1);
    byte |= ((!!(status&DataSerialIn))<<i);
  }
  //  printf("look at output bit\n"); getchar();

  MYoutb(0,BASEPORT);

  return byte;
}

void burn(unsigned int start, unsigned char *data, unsigned int length) {
  unsigned int ptr;

  printf("\r%4x",ptr);fflush(stdout);
  for(ptr=start; ptr<=(start+length); ptr++) {
    writebyte(data[ptr],ptr,1);
  }
  printf("\r%4x",ptr);fflush(stdout);

}

int main(int argc, char *argv[])
{
  int fd, ptr=0, maxbytes=32768;
  unsigned char *data;
  char *readfile=NULL, *writefile=NULL, opt;
  struct stat buf;
  struct sched_param p;
  time_t start;

  u_int16_t intv = 0xFFFF, bootv = 0xFFFF, nmiv = 0xFFFF, base = 0xC000;

  printf("%s\n",__TIME__);

  while(-1!=(opt=getopt(argc,argv,"b:r:w:i:b:n:"))) {
    switch(opt) {
    case 'b': {
      maxbytes = atoi(optarg);
      maxbytes = maxbytes>32768?32768:maxbytes;
    }break;
    case 'r': {
      readfile=(char *)malloc(strlen(optarg)+1);
      strcpy(readfile,optarg);
    }break;
    case 'w': {
      writefile=(char *)malloc(strlen(optarg)+1);
      strcpy(writefile,optarg);
    }break;
    case 'i': { // interrupt/BRK
      intv = strtoul(optarg,NULL,16);
    }break;
    case 'o': { // boot/reset
      bootv = strtoul(optarg,NULL,16);
    }break;
    case 'n': { // nmi
      nmiv = strtoul(optarg,NULL,16);
    }break;
    }
  }

  if(ioperm(BASEPORT, 3, 1)) {perror("ioperm");exit(1);}
  p.sched_priority = 1;
  sched_setscheduler(0, SCHED_RR, &p);
  fprintf(stderr, "Taken on soft realtime priority.\n");  
  
  if(writefile) {
    int cnt=0xf;
    fd = open(writefile,O_RDONLY);
    fstat(fd, &buf);
    printf("File opened, %i bytes long.\n",buf.st_size);
    if(buf.st_size>32768) {
      printf("File too big. (EEPROM is 32768 bytes big.) Exiting.\n");
      exit(1);
    }
    data=(unsigned char*)mmap(0,(buf.st_size<maxbytes)?buf.st_size:maxbytes,PROT_READ,MAP_SHARED,fd,0);
    printf("Beginning programming.\n");
    start=time(NULL);
    for(ptr=0;ptr<buf.st_size&&ptr<maxbytes;ptr++) {
#ifdef CSI
      if((ptr&0x7f)==0x7f) {
	writebyte(data[ptr],ptr,1); // last byte of a page
	printf("\r%4x",ptr);fflush(stdout);
      } else
	writebyte(data[ptr],ptr,0); // still writing in the same page
#else
      writebyte(data[ptr],ptr,1); // still writing in the same page
#endif

    }
    printf("\nTook %i seconds.\n",time(NULL)-start);
    munmap(data,(buf.st_size<maxbytes)?buf.st_size:maxbytes);
    close(fd);

    if(intv&bootv&nmiv!=0xFFFF) {
      printf("Writing 6502-vectors assuming EEPROM starts at $%x\n",base);
      if(intv!=0xFFFF) {
	writebyte( (intv&0xFF00)>>8, 0xFFFF - base, 1);
	writebyte(  intv&0xFF,       0xFFFE - base, 1);
      }
      if(bootv!=0xFFFF) {
	writebyte((bootv&0xFF00)>>8, 0xFFFF - base, 1);
	writebyte( bootv&0xFF,       0xFFFC - base, 1);
      }
      if(nmiv!=0xFFFF) {
	writebyte( (nmiv&0xFF00)>>8, 0xFFFB - base, 1);
	writebyte(  nmiv&0xFF,       0xFFFA - base, 1);
      }
    }
  }

  if(readfile) {
    fd = creat(readfile,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);

    printf("Beginning read.\n");
    for(ptr=0;ptr<maxbytes;ptr++) {
      unsigned char byte;
      byte = readbyte(ptr);
      write(fd,&byte,1);
      printf("\r%4x",ptr);fflush(stdout);
      usleep(1);
    }
    close(fd);
    printf("\n");
  }

  p.sched_priority = 0;
  sched_setscheduler(0, SCHED_OTHER, &p);
  if(ioperm(BASEPORT, 3, 0)) {perror("ioperm");exit(1);}

  exit(0);
}
