Lo-tech XT-CF flash utility source

From Lo-tech Wiki
Revision as of 22:33, 12 December 2013 by Lo-tech>James (Updated to 1.3-r02)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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: 09-Dec-13

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)


COMMAND LINE USAGE:

flash [image-file] [chip-base-address] (64k)
OR
flash interactive


Examples:

1. To overwrite the entire chip whose base address is D000, with the
   contents of an image file of up to 32KB:

   flash xtide.bin D000

2. As 1, but with an image file of up to 64KB:

   flash xtide.bin D000 64k

3. To use ineractive programming mode:

   flash interactive


In all cases, the ROM is completely erased and reprogrammed.
}


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.3-r02';

  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,
  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 := BytesRead1 + 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}


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}



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('1. Simple mode - write an image file to the ROM.  The ROM is');
  WriteLn('   completely erased, then the file copied.  Can be used with');
  WriteLn('   32KB or 64KB configurations.');
  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;
  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;
end;{procedure DisplayHelp}

var Ch : Char;

BEGIN
  WriteLn('Lo-tech XT-CF and 8-bit ROM Board Flash Programmer, version ',VERSION);
  if ParamSpecified('interactive') then InteractiveProgrammer
  else if ParamsOK( ParamStr(1), ParamStr(2) ) then
  begin
    FlashChip( ParamStr(1), ParamStr(2) );
    WriteLn;
    WriteLn('If BIOS images currently in use have been updated, it is HIGHLY');
    WriteLn('recommended that the computer is now restarted.');
    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}