/****
 * flashburn.c                  Andrew Huang                Xtreme Ideas  
 *                             bunnie@mit.edu
 *
 * version 1.0 <12/30/98>
 *  linux version of FLASH burner driver code, supports erasing, reading,
 *  writing, and basic file manipulation.
 *
 * NOTE: when compiling this under linux, you must use the -O2 compiler flag.
 *           for example, 
 *
 *       gcc -O2 flashburn.c -o flashburn
 *
 *    This is because the I/O port instructions turn into in-line assembly.
 *    Also, you must be super-user to run this program.
 ****/

/*** defines ***/
#if 0               /* dos compatibility commented out */
#undef  LINUX 0   
#define DOS 1
#endif

#if 1
#define  LINUX 1    
#undef DOS 
#endif

#undef  SCANARGV   /* scanargv library support; off for max compatibility */

/* open-loop wait parameters
   there is enough "play" in the loop such that this number does not really
   need to be adjusted even if this program is targetted for a variety of
   architectures */
#define WAIT  50

/*** includes ***/
#include "pport.h"
#include "flashburn.h"

/*** typedefs ***/

/*** globals ***/
char  srcfname[256];
char  dstfname[256];
FILE  *srcfile;
FILE  *dstfile;
unsigned char  *srcdata;
unsigned char  *dstdata;
unsigned long  srcsize;
unsigned long  dstsize;

/*** func protos ***/
void init_vars();
void init_ports();
void print_banner();
void set_FB_adr( unsigned char adr );
void set_FB_data( unsigned char data );
void set_PP_outmode();
void set_PP_inmode();
void FB_float();
void FB_engage();
void graceful_exit();
void do_writecycle( long wa, unsigned char wd );
void get_dstfname();
void get_srcfname();
void erase_sectors();
int get_ryby();
unsigned long sect_num2adr( int sector_number );
unsigned char doread_cycle( unsigned long wa );
void verify_flash();
void read_flash();
void program_flash();
void set_FB_reg( unsigned char data, unsigned char adr );
void graceful_exit();

void main( int argc, char *argv[] ) {
  /* main */
  /* locals */
  unsigned int i, count;
  long  wait;
  FILE *bitstream = NULL;
  char  c;
  int verbose = 1;
  char *fname;
  int choice;
  char data;

  /* body */
#if 0
#if SCANARGV
  scanargv( argc, argv, 
	    "%s [-v] [-f filename]", 
	    "[-v %+|-f %r]",
	    &verbose, &bitstream );
#else
  fname = argv[1];
  printf( "Using %s for FLASH image.\n", fname );
  if( NULL IS (bitstream = fopen( fname, "r" ) ) ) {
	printf( "Can't open %s...\n", fname );
	exit(0);
  } /* if */
  verbose = 0;
#endif
#endif

  if( verbose ) 
    print_banner();

  /***** 
   * init any machine-specific stuph 
   *****/
  init_vars();
  init_ports();

  /*****
   * initialize all registers to a known state
   *****/
  /* set address to inactive */
  set_FB_adr( FB_IDLE );
  FB_float();  /* make sure that the outputs aren't driving */
  set_PP_outmode();  /* set pport to output mode */
  set_FB_reg( FB_CS | FB_OE | FB_WR, FB_CTL );  /* set all ctl sigs inactive */
  
  set_FB_reg( 0, FB_DATA );
  set_FB_reg( 0, FB_ALO );
  set_FB_reg( 0, FB_AMID );
  set_FB_reg( 0, FB_AHI );

  /* we are all initialized */

  printf( "Please insert device in programming socket...\n" );
  getchar();


#if 0
  printf( "test loop...\n" );
  /* step 4: set pport to input mode */

  outb( 0xFF, LP1_DR );
  set_PP_inmode();

  data = inb( LP1_BASE + 0x402 );
  printf( "%02X\n", data & 0xFF );
  outb( 0x35, LP1_BASE + 0x402 );
  printf( "%02X\n", data & 0xFF );

  while(1){
  /* step 6: get the data! */
  data = (unsigned char) inb( LP1_DR );
  printf( "data: %02X\n", data & 0xFF );
  fflush(stdout);
  
  } 
#endif


  
  FB_engage();  /* turn on the output drivers */

  /*****
   * main interaction loop
   *****/
  while( 1 ) {
    printf( "\nWhat is thy bidding, my master?\n" );
    printf( "1) set source file for programming data\n" );
    printf( "2) set destination file for readback data\n" );
    printf( "3) program FLASH with data in source file\n" );
    printf( "4) erase sectors\n" );
    printf( "5) read FLASH and store in destination file\n" );
    printf( "6) verify device matches source file\n" );
    printf( "7) quit\n" );
    printf( "flashburn> " );
  
    scanf( "%d", &choice );
    getchar();  /* eat CR/LF */
  
    switch( choice ) {
    case 1:
      get_srcfname();
      break;
    case 2:
      get_dstfname();
      break;
    case 3:
      program_flash();
      break;
    case 4:
      erase_sectors();
      break;
    case 5:
      read_flash();
      break;
    case 6:
      verify_flash();
      break;
    case 7:
      graceful_exit();
      break;
    default:
      printf( "Unrecognized command.\n" );
      
    } /* switch choice */
  } /* while */

} /* main */


void verify_flash() {
  /* verify_flash */
  /* locals */

  /* body */
  printf( "For now, read contents to file and do a command line diff...\n" );
  return;

} /* verify_flash */



void read_flash() {
  /* read_flash */
  /* locals */
  unsigned long start, count, end;
  char startadrstr[256];
  char countstr[256];
  char response;
  unsigned long index;

  /* body */
  /* reset device into read mode */
  do_writecycle( 0x000, 0xF0 );
  
  printf( "Please enter start address for reading, in hex: " );
  scanf( "%s", startadrstr );
  getchar();
  
  start = strtoul( startadrstr, NULL, 16 );

  if( start > FLASH_SIZE ) {
    printf( "Start address is out of range. Aborting read.\n" );
    return;
  }

  printf( "Plesae enter how many bytes to read, in hex: " );
  scanf( "%s", countstr );
  getchar();
  count = strtoul( countstr, NULL, 16 );
  
  if( start + count > FLASH_SIZE ) {
    printf( "End of read region exceeds device capacity!\n" );
    printf( "Continue? (y/n) " );
    scanf( "%c", &response );
    getchar();
    if( response AINT 'y' ) {
      printf( "Aborting read...\n" );
      return;
    } /* if */
    printf( "Okay, read will be truncated!\n" );
    end = FLASH_SIZE;
  } /* if */
  else {
    end = start + count;
  }

  printf( "Reading..." );
  /* always zero-align the readback data for convenience sake */

  for( index = start; index < end; index++ ) {
    dstdata[index - start] = doread_cycle( index );
    
    if( !(index % DOT_PERIOD) ) {
      putchar('.');
      if( !(index % (DOT_PERIOD * 64) ) ) {
	putchar( '\n' );
      } /* if */
      fflush(stdout);
    } /* if */
  } /* for */

  printf( "\nRead completed, storing data in destination file.\n" );
  
  for( index = start; index < end; index++ ) {
    fwrite( &(dstdata[index - start]), 1, 1, dstfile );
  }
  
  fflush( dstfile );  /* flush the file, commit to disk */
  
  return;
} /* read_flash */


unsigned char doread_cycle( unsigned long wa ) {
  /* do_readcycle */
  /* locals */
  unsigned char data;
  
  /* body */
  /* step 0: make sure ctl lines are clear */
  set_FB_reg( FB_CS | FB_OE | FB_WR, FB_CTL );

  /* step 1: set addresses */
  set_FB_reg( wa & 0xFF, FB_ALO );
  set_FB_reg( (wa >> 8) & 0xFF, FB_AMID );
  set_FB_reg( (wa >> 16) & 0xFF, FB_AHI );
  
  /* step 2: set PP_INIT to float */
  FB_float();

  /* step 3: enable OE, CS */
  set_FB_reg( FB_WR, FB_CTL );

  /* step 4: set pport to input mode */
  outb( 0xFF, LP1_DR );
  set_PP_inmode();

  /* step 5: set PP_INIT to drive */
  FB_engage();

  /* step 6: get the data! */
  data = (unsigned char) inb( LP1_DR );
  
  /* step 7: float the PP pins again */
  FB_float();
  
  /* step 8: set pport to output mode */
  set_PP_outmode();

  /* step 9: disable OE and CS */
  set_FB_reg( FB_WR | FB_CS | FB_OE, FB_CTL );
  
  /* step 10: re-engage the device */
  FB_engage();

  /* step 11: return the data */
  return( data );

} /* doread_cycle */


void program_flash() {
  /* locals */
  unsigned long count;
  char  startadrstr[255];
  unsigned long start, end = 0;
  char  response;

  /* body */
  printf( "Please enter the start address in hex for programming: " );
  scanf( "%s", startadrstr );
  getchar();

  start = strtoul( startadrstr, NULL, 16 );
  
  if( start > (FLASH_SIZE - srcsize) ) {
    printf( "Start location %lX places content end beyond FLASH capacity.\n", start );
    printf( "Continue? (y/n) " );
    scanf( "%c", &response );
    getchar();
    if( response AINT 'y' ) {
      printf( "Aborting programming sequence...\n" );
      return;
    }
    printf( "Okay, data will be truncated!\n" );
    end = FLASH_SIZE - start;

  } /* if */
  else {
    end = srcsize;
  }

  printf( "Programming %ld bytes starting at %ld...\n", end, start );
  
  /* unlock bypass mode! */
  do_writecycle( 0x555, 0xAA );
  do_writecycle( 0x2AA, 0x55 );
  do_writecycle( 0x555, 0x20 );
  printf( "\nUnlock bypass mode engaged.\n" );

  printf( "Programming..." );
  for( count = 0; count < end; count++ ) {
    /* unlock bypass program cycles */
    do_writecycle( 0x000, 0xA0 );
    do_writecycle( start + count, srcdata[count] );
    while( !get_ryby() ) {
      ;  /* wait */
    } /* while */

    /* martching dots every 4K */
    if( !(count % DOT_PERIOD) ) {
      putchar( '.' );
      if( !( count % (DOT_PERIOD * 64) ) ) {
	putchar( '\n' );
      }
      fflush(stdout);
    } /* if */

  } /* for */
  
  /* unlock bypass reset! */
  do_writecycle( 0x000, 0x90 );
  do_writecycle( 0x000, 0x00 );

  printf( "\nUnlock bypass mode disengaged.\n" );

  printf( "Finished programming FLASH; please perform verify operation to check programming.\n" );

} /* program_flash */




void erase_sectors() {
  /* erase_sectors */
  /* locals */
  int sector_number;
  unsigned long sector_address;

  /* body */
  printf( "\nWhich sector would you like to erase (0-18, 19 is full chip)? " );
  scanf( "%d", &sector_number );
  getchar();
  
  if( sector_number < 0 OR sector_number > 19 ) {
    printf( "Sector number out of range.\n" );
    return;
  }
  if( sector_number IS 19 ) {
    printf( "Erasing entire chip.\n" );
  }
  else {
    printf( "Erasing sector %d.\n", sector_number );
  }
  printf( "Press return to commence erase.\n" );
  getchar();
  
  if( sector_number IS 19 ) {
    do_writecycle( 0x555, 0xAA );
    do_writecycle( 0x2AA, 0x55 );
    do_writecycle( 0x555, 0x80 );
    do_writecycle( 0x555, 0xAA );
    do_writecycle( 0x2AA, 0x55 );
    do_writecycle( 0x555, 0x10 );
    
    while( !get_ryby() ) {
      ;  /* wait */
    } /* while */
    printf( "Entire chip successfully erased.\n" );
  } /* if */
  else {
    do_writecycle( 0x555, 0xAA );
    do_writecycle( 0x2AA, 0x55 );
    do_writecycle( 0x555, 0x80 );
    do_writecycle( 0x555, 0xAA );
    do_writecycle( 0x2AA, 0x55 );

    sector_address = sect_num2adr( sector_number );
    
    do_writecycle( sector_address, 0x30 );
    
    while( !get_ryby() ) {
      ;  /* wait */
    } /* while */
    printf( "Sector %d successfully erased.\n", sector_number );
  } /* else */

} /* erase_sectors */


unsigned long sect_num2adr( int sector_number ) {
  /* sect_num2adr */
  /* locals */
  unsigned long temp;

  /* body */
  temp = 0L;

  if( sector_number >= 4 ) {
    temp = (sector_number - 3) << 16;
    return( temp );
  }
  else {
    switch( sector_number ) {
    case 0:
      temp = 0x0;
      break;
    case 1:
      temp = 0x04000;
      break;
    case 2:
      temp = 0x06000;
      break;
    case 3:
      temp = 0x08000;
      break;

    default:
      printf( "sect_num2adr(): Internal error.\n" );
      graceful_exit();
      break;
    } /* switch */

    return( temp );
  } /* else */

} /* sect_num2adr */

int get_ryby() {
  /* get_ryby */
  /*  return( (FB_RYBY & inb( LP1_SR )) ? 1 : 0 ); */
  return( 1 );

} /* get_ryby */


void get_srcfname() {
  /* get_srcfname */
  /* locals */
  long  count;

  /* body */
  printf( "\nSource file name (255 chars max): " );
  scanf( "%s", srcfname );
  printf( "Source file set to %s\n", srcfname );
  if( NULL IS (srcfile = fopen( srcfname, "r" ) ) ) {
	printf( "Can't open %s...\n", srcfname );
	graceful_exit();
  } /* if */
  printf( "Filehandle obtained.\n" );

  count = 0;
  while( (count < FLASH_SIZE) AND !feof( srcfile ) ) {
     fread( &(srcdata[count]), 1, 1, srcfile );
     count++;
  } /* while */
  printf( "Read %ld bytes into buffer.\n", count );

  srcsize = count;

} /* get_srcfname */


void get_dstfname() {
  printf( "\nDestination file name (255 chars max): " );
  scanf( "%s", dstfname );
  printf( "Destination file set to %s\n", dstfname );
  if( NULL IS (dstfile = fopen( dstfname, "w" ) ) ) {
	printf( "Can't open %s...\n", dstfname );
	graceful_exit();
  } /* if */
  printf( "Filehandle obtained.\n" );
}




void init_ports() {
  /* init_ports */
  /* locals */
  char data;

  /* body */
  
  /* if in linux mode, enable access to the parallel port I/O space.
	 you must be superuser to do this. */

#ifdef LINUX
  ioperm((unsigned long) LP1_BASE, (unsigned long) 0x3, 0x2 );
  iopl( 3 );
#endif

  /* force into "byte mode" for PS/2 compatibility mode */
  data = inb( LP1_BASE + 0x402 );
  printf( "ECR mode: %02X\n", data & 0xFF);
  outb( (data & 0x1F) | 0x20, LP1_BASE + 0x402 );  

  /* ECP ports uses bits 7, 6, 5 of the ECR (LP1_BASE + 0x402 as follows: */
  /* 000 SPP
     001 Byte
     010 fast centronics
     011 ECP
     100 EPP
     101 Reserved
     110 Test
     111 Config
     */

} /* init_ports */




void do_writecycle( long wa, unsigned char wd ) {
  /* do_writecycle */
  /* locals */

  /* body */
  /* step 0: make sure ctl lines are clear */
  set_FB_reg( FB_CS | FB_OE | FB_WR, FB_CTL );

  /* step 1: set addresses */
  set_FB_reg( wa & 0xFF, FB_ALO );
  set_FB_reg( (wa >> 8) & 0xFF, FB_AMID );
  set_FB_reg( (wa >> 16) & 0xFF, FB_AHI );
  
  /* step 2: set data */
  set_FB_reg( wd & 0xFF, FB_DATA );
  
  /* step 3: cycle the control pins */
  set_FB_reg( FB_OE, FB_CTL );
  set_FB_reg( FB_CS | FB_OE | FB_WR, FB_CTL );

  /* that's it! */
} /* do_writecycle */




void set_FB_reg( unsigned char data, unsigned char adr ) {
  /* set_FB_reg */
  /* locals */
  

  /* body */

  /*********** EXPECTATION **************/
  /* FB_adr is idle, OE is high so we don't have to frotz with the
     FB_float thing and PP_outmode thing. */
  outb( data & 0xFF, LP1_DR );
  
  set_FB_adr( adr );    /* commit the reg value */
  set_FB_adr( FB_IDLE );

} /* set_FB_reg */




void FB_float() {
  /* FB_float */
  char CR_temp;

  CR_temp = (char) inb( LP1_CR );
  
  CR_temp |= FB_FLOAT;  /* set the float bit */

  outb( CR_temp & 0xFF, LP1_CR );      /* commit change */
} /* FB_float */





void FB_engage() {
  /* FB_engage */
  char CR_temp;

  CR_temp = (char) inb( LP1_CR );
  
  CR_temp &= ~FB_FLOAT;  /* clear the float bit */

  outb( CR_temp, LP1_CR );      /* commit change */
} /* FB_engage */






void set_PP_outmode() {
  /* set_outmode */
  char CR_temp;

  CR_temp = (char) inb( LP1_CR );
  
  CR_temp &= ~CR_BID;  /* clear the bidirectional bit */

  outb( CR_temp, LP1_CR );      /* commit change */
} /* set_outmode */




void set_PP_inmode() {
  /* set_inmode */
  char CR_temp;

  CR_temp = (char) inb( LP1_CR );
  
  CR_temp |= CR_BID;  /* set the bidirectional bit */

  outb( CR_temp, LP1_CR );      /* commit change */
} /* set_inmode */




void set_FB_data( unsigned char data ) {

  outb( data & 0xFF, LP1_DR );

} /* set_FB_data */




void set_FB_adr( unsigned char adr ) {
  /* set_FB_adr */
  char  CR_state;

  CR_state = (char) inb( LP1_CR );
  /*  printf( "\nCR_state: %02X, ", CR_state & 0xFF ); */

  CR_state = ~FB_A0 & ~FB_A1 & ~FB_A2 & CR_state;  /* clear to one first */
  /* A* lines are inverted by hardware */

  /* now set bits to 0 if necessary */
  if( !(adr & 1) ) {
    CR_state |= FB_A0;
  } /* if */
  if( !(adr & 2) ) {
    CR_state |= FB_A1;
  } /* if */
  if( !(adr & 4) ) {
    CR_state |= FB_A2;
  } /* if */

  /*   printf( "CR_state: %02X ", CR_state & 0xFF ); */

  outb( CR_state & 0xFF, LP1_CR );   /* set the port state */

} /* set_FB_adr */





void print_banner() {
  /* print_banner */
  /* locals */

  /* body */
  printf( "flashburn v0.8 (12/30/98)\n" );

} /* print_banner */




void graceful_exit() {

  FB_float();   /* float the FB pins */
  exit(0);

}



void init_vars() {
  /* init_vars */
  /* locals */

  /* body */
  if( NULL IS (dstdata = (unsigned char *) calloc( FLASH_SIZE, sizeof( unsigned char ) ) ) ) {
    printf( "Can't allocate I/O buffers\n" );
    graceful_exit();
  }
  if( NULL IS (srcdata = (unsigned char *) calloc( FLASH_SIZE, sizeof( unsigned char ) ) ) ) {
    printf( "Can't allocate I/O buffers\n" );
    graceful_exit();
  }

  srcsize = 0;
  dstsize = 0;

} /* init_vars */
