Difference between revisions of "Lo-tech XT-CF flash utility source"

From Lo-tech Wiki
Jump to navigation Jump to search
Lo-tech>James
(Updated to 1.3-r02)
 
(→‎Code: Updated to version 1.4 - resolves long-standing bug preventing programming of ROMs that are exactly 64KB)
 
(One intermediate revision by one other user not shown)
Line 12: Line 12:
  
 
Written by:  James Pearce
 
Written by:  James Pearce
Last Updated: 09-Dec-13
+
Last Updated: 01-Aug-24
  
 
VERSION HISTORY:
 
VERSION HISTORY:
Line 30: Line 30:
 
1.3 - Added support for SST29EE010 and SST29LE010 flash chips
 
1.3 - Added support for SST29EE010 and SST29LE010 flash chips
 
       Corrected usDelayLoop (was calibrated at 500uS instead of 125uS)
 
       Corrected usDelayLoop (was calibrated at 500uS instead of 125uS)
 +
1.4 - Corrected bug writing 64K ROMs; removed interactive mode placeholder
  
  
 
COMMAND LINE USAGE:
 
COMMAND LINE USAGE:
  
flash [image-file] [chip-base-address] (64k)
+
flash [image-file] [chip-base-address]
OR
 
flash interactive
 
  
  
Line 42: Line 41:
  
 
1. To overwrite the entire chip whose base address is D000, with the
 
1. To overwrite the entire chip whose base address is D000, with the
   contents of an image file of up to 32KB:
+
   contents of an image file of between 2KB and 64KB:
  
 
   flash xtide.bin D000
 
   flash xtide.bin D000
  
2. As 1, but with an image file of up to 64KB:
 
  
  flash xtide.bin D000 64k
+
In all cases, the ROM is completely erased and reprogrammed, regardless
 
+
of the size of the image file.
3. To use ineractive programming mode:
 
 
 
  flash interactive
 
 
 
 
 
In all cases, the ROM is completely erased and reprogrammed.
 
 
}
 
}
  
Line 68: Line 60:
 
   RES_UNDER    :  Byte = 1;
 
   RES_UNDER    :  Byte = 1;
 
   RES_OVER      :  Byte = 2;
 
   RES_OVER      :  Byte = 2;
   VERSION      :  String = '1.3-r02';
+
   VERSION      :  String = '1.4';
  
 
   CHIP_UNKNOWN  :  String = 'Unknown';
 
   CHIP_UNKNOWN  :  String = 'Unknown';
Line 600: Line 592:
 
   CurrentPage :  PByte;
 
   CurrentPage :  PByte;
 
   RomFile    :  File;
 
   RomFile    :  File;
   BytesRead,
+
   BytesRead   :  Longint;
 
   BytesRead1,
 
   BytesRead1,
 
   BytesRead2,
 
   BytesRead2,
Line 650: Line 642:
 
   if not EoF(RomFile) then
 
   if not EoF(RomFile) then
 
     BlockRead(RomFile,RomImage2^,32768,BytesRead2);
 
     BlockRead(RomFile,RomImage2^,32768,BytesRead2);
   BytesRead := BytesRead1 + BytesRead2;
+
   BytesRead := Longint(BytesRead1) + Longint(BytesRead2);
 
   WriteLn( BytesRead, ' bytes read OK.');
 
   WriteLn( BytesRead, ' bytes read OK.');
 
   Close(RomFile);
 
   Close(RomFile);
Line 768: Line 760:
 
     BaseOK := False;
 
     BaseOK := False;
 
end;{function BaseOK}
 
end;{function BaseOK}
 
 
Procedure InteractiveProgrammer;
 
begin
 
  WriteLn('Interactive programming feature not implemented yet.');
 
  WriteLn;
 
  WriteLn('Can you help?  Please get in touch via the blog:');
 
  WriteLn(' http://www.lo-tech.co.uk/about/');
 
  WriteLn;
 
end;{procedure}
 
  
  
Line 828: Line 810:
 
   WriteLn('Usage:');
 
   WriteLn('Usage:');
 
   WriteLn;
 
   WriteLn;
   WriteLn('1. Simple mode - write an image file to the ROM.  The ROM is');
+
   WriteLn('Write an image file to the ROM.  The ROM is completely');
   WriteLn('   completely erased, then the file copied.  Can be used with');
+
   WriteLn('erased, then the file copied.  Can be used with ROMs');
   WriteLn('   32KB or 64KB configurations.');
+
   WriteLn('between 2KB and 64KB. Note: Checksum is not calculated.');
 
   WriteLn;
 
   WriteLn;
 
   WriteLn('eg: flash [image-file] [base-address]');
 
   WriteLn('eg: flash [image-file] [base-address]');
Line 837: Line 819:
 
   WriteLn('  [base-address] - hex ROM window base address, i.e. D000');
 
   WriteLn('  [base-address] - hex ROM window base address, i.e. D000');
 
   WriteLn('                  must be A000-F800');
 
   WriteLn('                  must be A000-F800');
  WriteLn;
 
  WriteLn('2. Interactive mode.  Enables a number of different ROM images');
 
  WriteLn('  to be stored in the chip and programmed independently.  The');
 
  WriteLn('  ROM contents index will be kept in a file on disk.');
 
  WriteLn;
 
  WriteLn('eg: flash interactive');
 
 
   WriteLn;
 
   WriteLn;
 
end;{procedure DisplayHelp}
 
end;{procedure DisplayHelp}
Line 850: Line 826:
 
BEGIN
 
BEGIN
 
   WriteLn('Lo-tech XT-CF and 8-bit ROM Board Flash Programmer, version ',VERSION);
 
   WriteLn('Lo-tech XT-CF and 8-bit ROM Board Flash Programmer, version ',VERSION);
   if ParamSpecified('interactive') then InteractiveProgrammer
+
   If ParamsOK( ParamStr(1), ParamStr(2) ) then
  else if ParamsOK( ParamStr(1), ParamStr(2) ) then
 
 
   begin
 
   begin
 
     FlashChip( ParamStr(1), ParamStr(2) );
 
     FlashChip( ParamStr(1), ParamStr(2) );
 
     WriteLn;
 
     WriteLn;
     WriteLn('If BIOS images currently in use have been updated, it is HIGHLY');
+
     WriteLn('If BIOS images currently in use have been updated, you MUST');
     WriteLn('recommended that the computer is now restarted.');
+
     WriteLn('now reboot.');
 
     WriteLn;
 
     WriteLn;
 
     Write('Press any key to return to DOS, or CTRL-ALT-DEL to restart.');
 
     Write('Press any key to return to DOS, or CTRL-ALT-DEL to restart.');

Latest revision as of 19:55, 26 August 2024

Source code for a simple lo-tech XT-CF flash utility, which can be compiled using Turbo Pascal 6. Compiled code can be downloaded here.

Code

Program Flash;

{
Simple utility to read or write ROM images to or from SST39SF or AMIC A29010
flash chips, as used by the lo-tech XT-CF and Universal ROM Boards.

See http://www.lo-tech.co.uk/wiki/XT-CF-Boards

Written by:   James Pearce
Last Updated: 01-Aug-24

VERSION HISTORY:

0.1 - Initial beta
0.2 - Added custom delay routines, to make it work on Pentium and higher
0.3 - Corrected maximum delay loop setting
1.0 - Initial 'finished' release for Peacon XT-CF board:
      - removed ROM size parameter (as ROM is hard-wired to be 32KB)
1.1 - Updated release to support Peacon 8-Bit ROM Board:
      - Included prompt to confirm writing
      - Added (back) ROMSIZE parameter, as it could be 32 or 64KB
      - Added ability to merge code
1.2 - Rebranded to lo-tech
      Added support for AMIC A29010A 1Mb flash chip
      ROMSIZE parameter removed (again) as not needed; just use an image > 32K
1.3 - Added support for SST29EE010 and SST29LE010 flash chips
      Corrected usDelayLoop (was calibrated at 500uS instead of 125uS)
1.4 - Corrected bug writing 64K ROMs; removed interactive mode placeholder


COMMAND LINE USAGE:

flash [image-file] [chip-base-address]


Examples:

1. To overwrite the entire chip whose base address is D000, with the
   contents of an image file of between 2KB and 64KB:

   flash xtide.bin D000


In all cases, the ROM is completely erased and reprogrammed, regardless
of the size of the image file.
}


uses dos, crt;


CONST
  FLASH_OK      :   BOOLEAN = TRUE;
  FLASH_ERROR   :   BOOLEAN = FALSE;
  RES_OK        :   Byte = 0;
  RES_UNDER     :   Byte = 1;
  RES_OVER      :   Byte = 2;
  VERSION       :   String = '1.4';

  CHIP_UNKNOWN  :   String = 'Unknown';
  SST29EE010    :   String = 'SST 29EE010';
  SST29LE010    :   String = 'SST 29LE010';
  SST512Kb      :   String = 'SST 512Kb';
  SST1Mb        :   String = 'SST 1Mb';
  SST2Mb        :   String = 'SST 2Mb';
  SST4Mb        :   String = 'SST 4Mb';
  AMIC1Mb       :   String = 'AMIC A29010';


TYPE
  PByte         =   ^BYTE;


VAR
  {global variables for the delay routines}
  DelayCounter  :  LongInt;
  Hr, Mn,
  Sec, S100     :  Word;

  {global variables recording chip type}
  ChipType     :   String;



procedure start_clock;
{stores the current system clock time in static globals, which
 can then be used as a time base by function stop_clock at some
 later point}
begin
  GetTime(Hr,Mn,Sec,S100);
end;


function stop_clock : longint;
{returns number of miliseconds since start_clock was called,
 assuming the day remains the same that is}
var h, m, s, cs : word;
begin
  GetTime(h,m,s,cs);
  stop_clock := ((h*3600000)+(m*60000)+(s*1000)+(cs*10)) -
                ((Hr*3600000)+(Mn*60000)+(Sec*1000)+(S100*10));
end;{function stop_clock}


procedure delayLoop( ms : word );
{creates a delay of ms miliseconds (once calibrated) by running
 some DIV instructions}
var
  a, b, c  : word;
  x	   : word;
  y        : LongInt;

begin
  {first check if the in-built delay can be used}
  if DelayCounter < 1 then Delay(ms)
  else begin
    c := WORD(DelayCounter AND $FFFF);
    for x := 1 to ms do
    begin
      for y := 1 to DelayCounter do
      begin
        a := b div c;
        inc(b);
      end;{for}
    end;{for x}
  end;{if/else}
end;{procedure}


procedure usdelayLoop( count : word );
{creates a delay of count x 125 microseconds (once calibrated) by running
 some DIV instructions}
var
  a, b, c  : word;
  x	   : word;
  y, ShortWait : LongInt;

begin
  c := WORD(DelayCounter AND $FFFF);
  ShortWait := DelayCounter SHR 3; {ms to 125us}
  for x := 1 to count do
  begin
    for y := 1 to ShortWait do
    begin
      a := b div c;
      inc(b);
    end;{for}
  end;{for x}
end;{procedure}


function test_delay : byte;
var
  interval : longint;

begin
  start_clock;
  delayLoop(110); {testing for 110ms delay}
  interval := stop_clock;
  if interval < 110 then test_delay := RES_UNDER
  else if interval > 110 then test_delay := RES_OVER
  else test_delay := RES_OK;
end;{function test_delay}


procedure calibrate_delay;
var
  res       :  byte;
  interval  :  LongInt;

begin
  {first check if the in-built delay procedure can be used.  Otherwise
   an XT will take a long time calibrating, for no purpose}
  start_clock;
  delayLoop(110); {testing for 110ms delay}
  interval := stop_clock;
{  if interval = 110 then
    DelayCounter := -1 {built-in procedure will be used}
{  else}
  begin
    {calibration required.}
    {div is c.80 clocks on an 8088 => ~60 divs per ms}
    DelayCounter := 30;
    res := RES_UNDER;
    while res <> RES_OK do
    begin
      res := test_delay;
      if DelayCounter >= $40000000 then
      begin
        {we can't calibrate properly as we're about to overflow}
        RES := RES_OK; {end the loop}
        DelayCounter := $7FFFFFFF; {maximum value as it's signed}
      end else
      begin
        if res = RES_UNDER then DelayCounter := DelayCounter * 2; {we need more delay}
        if res = RES_OVER then DelayCounter := DelayCounter * 2 div 3; {we've overshot}
      end;{if/else}
    end;{while}
  end;{if interval/else}
end;{procedure calibrate_delay}
  

function HexToWord( s : string ) : word;
{converts the hex represented by the string, to a WORD}
var
  i     :  Byte;
  Wd    :  Word;
  Digit :  Byte;
  Error :  Boolean;
begin
  Wd := 0; Error := False;
  if (length(s) <= 4) then begin
    for i := 1 to length(s) do begin
      case s[i] of
        '0'..'9' : Digit := BYTE(s[i]) - 48;
        'A'..'F' : Digit := 10 + (BYTE(s[i]) - 65);
        'a'..'f' : Digit := 10 + (BYTE(s[i]) - 97);
      else Error := True;
      end;{case}
      if Error then i := length(s)
      else Wd := (Wd SHL 4) + Digit;
    end;{for}
  end;{if}
  if Error then HexToWord := 0
  else HexToWord := Wd;
end;{function HexToWord}


function WordToHex( Wd : word ) : string;
{converts a word to ASCII hex}
var
  Digit, i   :  Byte;
  AsciiDigit :  Char;
  s          :  string;
begin
  BYTE(s[0]) := 4; {set string length}
  for i := 4 downto 1 do
  begin
    {get the low 4 bits of Wd to Digit, and SHR Wd 4}
    asm
      mov    ax, Wd
      mov    bx, ax
      and    bl, $0F
      cmp    bl, 10
      jb     @ZeroToNine
      add    bl, 17
      @ZeroToNine:
      add    bl, 48
      mov    AsciiDigit, bl
      shr    ax, 1
      shr    ax, 1
      shr    ax, 1
      shr    ax, 1
      mov    Wd, ax
    end;{asm}
    s[i] := AsciiDigit;
  end;{for i}
  WordToHex := s;
end;{function WordToHex}


function Caps( s : string ) : string;
{returns string capitalised}
var
  i       : byte;
  tempstr : string;

begin
  BYTE(TempStr[0]) := BYTE(s[0]); {set length}
  for i := 1 to 255 do
    TempStr[i] := UpCase(s[i]); {copy all chars}
  Caps := TempStr;
end;{function Caps}


function FlashType( baseaddr : word ) : Boolean;
{Attempts to determine flash chip type from supported types.
 Returns FLASH_OK if determined properly, FLASH_ERROR otherwise.
 Result itself is stored in global variable ChipType}
var
  Location  : ^byte;

begin
  ChipType := CHIP_UNKNOWN;

  {First check for SST39SF chips}
  {ChipID routine entry...}
  Location := ptr( baseaddr, $5555 );
  Location^ := $AA;
  Location := ptr( baseaddr, $2AAA );
  Location^ := $55;
  Location := ptr( baseaddr, $5555 );
  Location^ := $90;

  {check what's there}
  {v1.3 - added 125uS delay, required by SST29EE010 before polling chip ID}
  usdelayLoop(1);

  Location := ptr( baseaddr, 0 );
  If Location^ = $BF then
  begin
    Location := ptr( baseaddr, 1 );
    case Location^ of
      $07 : ChipType := SST29EE010;
      $08 : ChipType := SST29LE010;
      $B4 : ChipType := SST512Kb;
      $B5 : ChipType := SST1Mb;
      $B6 : ChipType := SST2Mb;
      $B7 : ChipType := SST4Mb;
    end; {case}
    {Exit ChipID routine...}
    Location := ptr( baseaddr, $5555 );
    Location^ := $AA;
    Location := ptr( baseaddr, $2AAA );
    Location^ := $55;
    Location := ptr( baseaddr, $5555 );
    Location^ := $F0;
    usdelayLoop(1); {125uS delay; required by SST29EE010}
  end {if SST chip detected}
  else
  begin
    {SST not detected; check now for AMIC A29010}
    {Autoselect mode routine entry...}
    Location := ptr( baseaddr, $555 );
    Location^ := $AA;
    Location := ptr( baseaddr, $2AA );
    Location^ := $55;
    Location := ptr( baseaddr, $555 );
    Location^ := $90;
    
    {check what's there}
    Location := ptr( baseaddr, 0 );
    If Location^ = $37 then
    begin
      Location := ptr( baseaddr, 1 );
      case Location^ of
        $A4 : ChipType := AMIC1Mb;
      end;{case}
      {Exit Autoselect mode by issuing reset...}
      Location^ := $F0;
    end;{if AMIC chip detected}
  end;{if/else}

  {return result}
  If ChipType = CHIP_UNKNOWN then
    FlashType := FLASH_ERROR
  else
    FlashType := FLASH_OK;
end;{function FlashType}


function eraseChip( baseaddr : word ) : boolean;
{erases the entire chip; returns FLASH_OK if successful}
var
  Location   : ^byte;
  DQ7        : Byte;
  Done       : Boolean;
  Error      : Boolean;
  LoopCount,
  LoopMax    : Word;
  DivRes     : Byte;
  lastToggle,
  thisToggle : Byte;

begin
  {Route depends on the chip type}
  If (ChipType = AMIC1Mb) or (ChipType = CHIP_UNKNOWN) then
  begin
    {AMIC A29010 erase command sequence}
    Location := ptr( baseaddr, $555 );
    Location^ := $AA;
    Location := ptr( baseaddr, $2AA );
    Location^ := $55;
    Location := ptr( baseaddr, $555 );
    Location^ := $80;
    Location := ptr( baseaddr, $555 );
    Location^ := $AA;
    Location := ptr( baseaddr, $2AA );
    Location^ := $55;
    Location := ptr( baseaddr, $555 );
    Location^ := $10;
    LoopMax := 1875; {set timeout to 75s (1875 x 40ms)}
  end else begin
    {SST39SF and SST29xx erase command code...}
    Location := ptr( baseaddr, $5555 );
    Location^ := $AA;
    Location := ptr( baseaddr, $2AAA );
    Location^ := $55;
    Location := ptr( baseaddr, $5555 );
    Location^ := $80;
    Location := ptr( baseaddr, $5555 );
    Location^ := $AA;
    Location := ptr( baseaddr, $2AAA );
    Location^ := $55;
    Location := ptr( baseaddr, $5555 );
    Location^ := $10;
    LoopMax := 25; {set timeout to 1s (25 x 40ms)}
  end;{Chip specific erase command entry select}

  {then wait}
  Done := False;
  Error := False;
  LoopCount := 0;
  Location := ptr( baseaddr, 0 ); {we'll poll address 0 to check when done}

  if (ChipType = SST29EE010) or (ChipType = SST29LE010) then
  begin
    usDelayLoop(2); {250uS delay before we can poll}
    {SST29xE010 provides a busy toggle bit (DQ6) that must be polled}
    lastToggle := Location^ AND $80; {get DQ6 from device}
    while (not done) and (not error) do
    begin
      inc(LoopCount);
      delayLoop(40); {40ms delay}
      thisToggle := Location^ AND $80; {get DQ6 from device}
      if (thisToggle = lastToggle) then Done := true;
      lastToggle := thisToggle;
      if LoopCount mod 100 = 0 then write('.'); {provide an indication things are still running}
      if (LoopCount = LoopMax) or KeyPressed then Error := true; {trap timeout based on chip type}
    end;{while}
    if (Done) then begin
      {erase seemed to succeed, so enable SDP}
      Location := ptr( baseaddr, $5555 );
      Location^ := $AA;
      Location := ptr( baseaddr, $2AAA );
      Location^ := $55;
      Location := ptr( baseaddr, $5555 );
      Location^ := $A0;
    end;{enable SDP}
  end else begin
    {other chips are handled with data polling}
    while (not done) and (not error) do
    begin
      inc(LoopCount);
      delayLoop(40); {40ms delay}
      if Location^ = $FF then Done := true;
      if LoopCount mod 100 = 0 then write('.'); {provide an indication things are still running}
      if (LoopCount = LoopMax) or KeyPressed then Error := true; {trap timeout based on chip type}
    end;{while}
  end;{if/else}

  If Error then
  begin
    If KeyPressed then write('Interrupted by keypress.')
    else begin
      WriteLn('** Timeout **');
      Write('Expected FFh, but found ',WordToHex(Location^),'h.');
    end;{else}
    eraseChip := FLASH_ERROR;
  end else eraseChip := FLASH_OK;
end;{function eraseChip}


function writeByte( baseaddr : word; offset : word; b : byte ) : BOOLEAN;
{writes byte b to baseaddr:offset, returns FLASH_OK if successful}
var
  Location   : ^byte;
  DQ7        : Byte;
  Done       : Boolean;
  Error      : Boolean;
  DivRes     : byte;
  usDelay,
  LoopCount,
  LoopMax    : Word;

begin
  {Routine depends on the chip type}
  If (ChipType = AMIC1Mb) or (ChipType = CHIP_UNKNOWN) then
  begin
    {AMIC A29010 erase command sequence}
    Location := ptr( baseaddr, $555 );
    Location^ := $AA;
    Location := ptr( baseaddr, $2AA );
    Location^ := $55;
    Location := ptr( baseaddr, $555 );
    Location^ := $A0;
    Location := ptr( baseaddr, offset );
    Location^ := b;
    usDelay := 1; {1 = 125us delay per byte}
    LoopMax := 3; {timeout after 375us (3x125)}
  end else begin
    {SST39SF byte program command sequence}
    Location := ptr( baseaddr, $5555 );
    Location^ := $AA;
    Location := ptr( baseaddr, $2AAA );
    Location^ := $55;
    Location := ptr( baseaddr, $5555 );
    Location^ := $A0;
    Location := ptr( baseaddr, offset );
    Location^ := b;
    usDelay := 1; {1 = 125us delay per byte}
    LoopMax := 2; {timeout after 250us (2x125)}
  end;{if/else}

  {then wait}
  Done := False;
  Error := False;
  LoopCount := 0;
  while (not done) and (not error) do
  begin
    inc(LoopCount);
    usdelayLoop(usDelay);
    if Location^ = b then Done := true;
    if (LoopCount = LoopMax) or KeyPressed then Error := true;
  end;{while}

  If Error then writeByte := FLASH_ERROR
  else writeByte := FLASH_OK;
end;{function writeByte}
 

function writePage( baseaddr, pageaddr : word; var PageData : PByte ) : BOOLEAN;
{writes a 128 byte page to the device, returns FLASH_OK if successful}
{baseaddr = ROM chip base address
 pageaddr = offset of page to be programmed (0, 128, 256...)
 pagedata = pointer to 128 bytes of data to be programmed.}
var
  Location   : ^byte;
  DQ7        : Byte;
  Done       : Boolean;
  Error      : Boolean;
  DivRes     : byte;
  usDelay,
  LoopCount,
  LoopMax    : Word;
  i          : byte;

begin
  Error := True;
  {Routine depends on the chip type}
  if (ChipType = SST29EE010) or (ChipType = SST29LE010) then
  begin
    Location := ptr( baseaddr, 0 );
    asm cli end; {clear interrupts - each bytes has to be sent within 100uS}
    {SST29xE page SDP unlock sequence}
    Location := ptr( baseaddr, $5555 );
    Location^ := $AA;
    Location := ptr( baseaddr, $2AAA );
    Location^ := $55;
    Location := ptr( baseaddr, $5555 );
    Location^ := $A0;

    {device should now (immediately) accept 128 bytes of data}
    Location := ptr( baseaddr, pageaddr );
    for i := 1 to 128 do
    begin
      Location^ := PageData^;
      inc(Location);
      inc(PageData);
    end;{for}

    {data sent to buffer - now wait for data to be ingested}
    asm sti end; {re-enable interupts}
    usdelayLoop(2); {250uS delay before we can check}

    {then wait}
    Done := False;
    Error := False;
    LoopCount := 0;
    usDelay := 4; {4 = 500us delay per byte}
    LoopMax := 20; {timeout after 10ms (20 x 500us)}

    while (not done) and (not error) do
    begin
      inc(LoopCount);
      usdelayLoop(usDelay);
      if Location^ = PageData^ then Done := true;
      if (LoopCount = LoopMax) or KeyPressed then Error := true;
    end;{while}

  end;{if/else}
  If Error then writePage := FLASH_ERROR
  else writePage := FLASH_OK;
end;{function writePage}
 

procedure FlashChip( imagefile, baseaddr : string );
{called once parameters are validated; this does the work!}
{$R- disable range checking, as the ROM is directly accessed and could be
     64K exactly}
type
  TRomImage   =  array[0..31767] of byte;

var
  RomImage1,
  RomImage2   :  ^TRomImage;
  Rom         :  ^TRomImage; {used to compare}
  CurrentPage :  PByte;
  RomFile     :  File;
  BytesRead   :  Longint;
  BytesRead1,
  BytesRead2,
  CurrentPos  :  word;
  Res         :  Integer;
  RomBaseAddr :  word;
  Error       :  Boolean;
  X, Y        :  Byte;
  UpdateInt   :  word;
  UpdateCount :  word;
  Percent     :  Byte;
  Ch          :  Char;
  CurrentByte :  Byte;

begin
  {load the ROM image file}
  write('Opening ROM image... ');
  new(RomImage1); new(RomImage2);
  assign(RomFile,imagefile);
  {$I- disable IO error checking}
  reset(RomFile,1);
  {$I+ }
  if IOResult <> 0 then
  begin
    WriteLn('Unable to open file.');
    Exit;
  end;{if}
  if (FileSize(RomFile) < 2048) or (FileSize(RomFile) > 65536) then
  begin
    {ROM file too small or too large}
    WriteLn('ROM file must be between 2 and 64K.');
    Close(RomFile);
    Exit;
  end else WriteLn('OK');

  {get chip base address}
  RomBaseAddr := HexToWord( baseaddr ); {starting location of ROM}

  {Display detected flash type}
  If FlashType(RomBaseAddr) = FLASH_OK then
    WriteLn( 'Detected ', ChipType, ' Flash Chip at ', baseaddr, 'h' )
  else
    WriteLn( 'Unable to determine flash chip type.  Attempting JDEC programming.');

  {read the file}
  Write('Reading... ');
  BytesRead2 := 0;
  BlockRead(RomFile,RomImage1^,32768,BytesRead1);
  if not EoF(RomFile) then
    BlockRead(RomFile,RomImage2^,32768,BytesRead2);
  BytesRead := Longint(BytesRead1) + Longint(BytesRead2);
  WriteLn( BytesRead, ' bytes read OK.');
  Close(RomFile);

  {erase the chip}
  Write('Erasing ' );
  if (eraseChip(RomBaseAddr) = FLASH_OK) then
  begin
    {erase was successful; write out new code}
    writeLn('OK');
    Error := False;

    If BytesRead > 0 then
    begin
      WriteLn('Programming... ');
      UpdateCount := 0;
      Percent := 0;
      X := WhereX;
      Y := WhereY;
      write( '0%' );

      if (ChipType = SST29EE010) or (ChipType = SST29LE010) then
      begin
        {page-mode programming}
        while CurrentPos < Pred(BytesRead) do
        begin
          if CurrentPos < 32768 then
            CurrentPage := Addr(RomImage1^[CurrentPos])
          else
            CurrentPage := Addr(RomImage2^[CurrentPos-32768]);
          if (writePage(RomBaseAddr,CurrentPos,CurrentPage) = FLASH_ERROR) then
          begin
            GotoXY(X,Y);
            Writeln('Failed writing at page ',(CurrentPos div 128));
            CurrentPos := Pred(BytesRead); {terminate loop}
            Error := TRUE; {record error state}
          end else begin
            {programming that page succeeded - update screen and counters}
            CurrentPos := CurrentPos + 128; {advance to the next page}
            Percent    := (CurrentPos*100) div BytesRead;
            GotoXY(X,Y);
            write(Percent,'%');
          end;{if (writePage...}
        end;{while}
      end else begin
        {byte-mode programming}
        UpdateInt := BytesRead div 100;
        for CurrentPos := 0 to Pred(BytesRead) do
        begin
          inc(UpdateCount);
          if (UpdateCount = UpdateInt) then
          begin
            {update percentage displayed on-screen}
            GotoXY(X,Y);
            inc(Percent);
            write(Percent,'%');
            UpdateCount := 0;
          end;{screen update}
          if CurrentPos < 32768 then
            CurrentByte := RomImage1^[CurrentPos]
          else
            CurrentByte := RomImage2^[CurrentPos-32768];
          if (writeByte(RomBaseAddr,CurrentPos,CurrentByte) = FLASH_ERROR) then
          begin
            GotoXY(X,Y);
            Writeln('Failed writing at byte ',CurrentPos);
            CurrentPos := Pred(BytesRead); {terminate loop}
            Error := TRUE; {record error state}
          end;{if}
        end;{for}
      end;{if ChipType.../else}

      if Not Error then begin
        {write was OK; now check what's there}
        GotoXY(X,Y);
        WriteLn('OK  ');
        Write('Comparing... ');
        Rom := Ptr( RomBaseAddr, 0 ); {access the ROM directly}
        for CurrentPos := 0 to Pred(BytesRead) do
        begin
          if CurrentPos < 32768 then
            CurrentByte := RomImage1^[CurrentPos]
          else
            CurrentByte := RomImage2^[CurrentPos-32768];
          if Rom^[CurrentPos] <> CurrentByte then
          begin
            Error := True;
            WriteLn('Error at offset ', WordToHex(CurrentPos), 'h');
            CurrentPos := Pred(BytesRead);
          end;{if}
        end;{for}
        If Not Error then WriteLn('OK!');
      end;{if not error / else}
    end;{if BytesRead > 0}
  end {if eraseChip}
  else
    {chip erase failed}
    WriteLn('Erase operation FAILED.');
  Dispose(RomImage1); Dispose(RomImage2);
end;{procedure FlashChip}


function BaseOK( s : string ) : Boolean;
{checks that the base address i:
  - in the correct range, A000 - F000
  - 4K aligned
}
var
  BaseAddr : Word;

begin
  BaseAddr := HexToWord( s );
  if ((BaseAddr AND $FF) = 0) and (BaseAddr >= $A000) and
     (BaseAddr <= $F000) then
    BaseOK := True
  else
    BaseOK := False;
end;{function BaseOK}



function ParamSpecified(s : string) : boolean;
{checks all command line arguments for s, returning true if found}
var i : word; found : boolean;
begin
  found := false;
  for i := 1 to ParamCount do
    if Copy(ParamStr(i),1,Length(s)) = s then found := true;
  ParamSpecified := found;
end;{function ParamSpecified}



Function ParamsOK( P1, P2 : string ) : Boolean;
{checks parameters, returns true if look OK}
var
  f : file;
  OK  :  Boolean;

begin
  OK := True;
  assign(f,P1);
  {$i- } reset(f); {$i+ }
  if IOResult <> 0 then begin
    OK := False;
    WriteLn('Couldn''t open file ', P1, '.');
  end else begin
    {found file OK}
    close(f);
    if not BaseOK(P2) then OK := False;
  end;
  if OK then
  begin
    {parameters suggest we're good to go - calibrate the timing loops}
    write('Calibrating delay loops...');
    calibrate_delay;
    writeLn(' calibration factor is ', DelayCounter, '.');
  end;{if OK}
  ParamsOK := OK;
end;{function ParamsOK}


Procedure DisplayHelp;
begin
  WriteLn('Supports SST 29xE010/39SF0x0, and AMIC A29010 flash chips.');
  WriteLn;
  WriteLn('Usage:');
  WriteLn;
  WriteLn('Write an image file to the ROM.  The ROM is completely');
  WriteLn('erased, then the file copied.  Can be used with ROMs');
  WriteLn('between 2KB and 64KB. Note: Checksum is not calculated.');
  WriteLn;
  WriteLn('eg: flash [image-file] [base-address]');
  WriteLn;
  WriteLn('  [image-file]   - ROM image, i.e. xtide.bin');
  WriteLn('  [base-address] - hex ROM window base address, i.e. D000');
  WriteLn('                   must be A000-F800');
  WriteLn;
end;{procedure DisplayHelp}

var Ch : Char;

BEGIN
  WriteLn('Lo-tech XT-CF and 8-bit ROM Board Flash Programmer, version ',VERSION);
  If ParamsOK( ParamStr(1), ParamStr(2) ) then
  begin
    FlashChip( ParamStr(1), ParamStr(2) );
    WriteLn;
    WriteLn('If BIOS images currently in use have been updated, you MUST');
    WriteLn('now reboot.');
    WriteLn;
    Write('Press any key to return to DOS, or CTRL-ALT-DEL to restart.');
    {clear keyboard buffer, in case user interrupted the process}
    while keypressed do Ch := ReadKey;
    {now pause}
    repeat until keypressed;
    if keypressed then Ch := Readkey;  
  end
  else DisplayHelp;
END.{program}