/*
 **************************************************************************
 *
 * FLASH - Program FlashCard flash EPROM
 *
 * Module:  flash.S86
 * Purpose: Program FlashCard flash EPROM
 * Entries: _start
 *
 **************************************************************************
 *
 * Copyright (C) 1995-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **************************************************************************
 *
 */

 
/*
 **************************************************************************
 *
 * The purpose of this program is to program the flash EPROM on the FlashCard
 * ISA card. The flash EPROM has to be an AMD 29F010, and the programming
 * algorithm is the same as that suggested by AMD in the appropriate data
 * sheets. This program only runs on the DOS operating system.
 */
 
#include <common.i86>
#include <system/bios.i86>




/* 
 **************************************************************************
 *
 * Miscellaneous defines:
 */
#define INT_DOS		0x21		/* DOS interrupt */
#define CMDLINE		0x0080		/* offset of command line into PSP */

#define BUFSIZE		512		/* size of read buffer */
#define FNAMSIZE	64		/* maximum size of filename */
#define MAXFILE		32768		/* maximum file size */

#define SEGLOW		0xC800		/* lower range for EPROM segment */
#define SEGHIGH		0xE800		/* upper range for EPROM segment */
#define SEGMASK		0xFE00		/* mask for 8kB boundary */

#define AMD_ID		0x2001		/* flash EPROM ID, only support AMD */

#define ERASE1_CMD	0x80		/* first cmd for erasing full chip */
#define ERASE2_CMD	0x10		/* second cmd for erasing full chip */
#define READID_CMD	0x90		/* command to read chip ID */
#define PROG_CMD	0xA0		/* command to program a byte */
#define RESET_CMD	0xF0		/* command to reset chip state machine */


 
.file __FILE__
.line __LINE__
/*
 **************************************************************************
 *
 * Constant segment:
 */

		_rodata

cpymsg:		.ascii	"flash - Flash EPROM programming utility"
		.byte	0x0D,0x0A
		.ascii	"Copyright (C) 1996-2007 Gero Kuhlmann"
		.byte	0x0D,0x0A
		.byte	0x0D,0x0A,0x24

segerr:		.ascii	"Illegal segment "
		.ascii	"(must be between C800 and E800 on 8kB boundary)"
		.byte	0x0D,0x0A,0x24

useerr:		.ascii	"usage: flash <seg-addr> <file name>"
		.byte	0x0D,0x0A,0x24

filerr:		.ascii	"Cannot open input file"
		.byte	0x0D,0x0A,0x24

sekerr:		.ascii	"Unable to seek in input file"
		.byte	0x0D,0x0A,0x24

sizerr:		.ascii	"Invalid input file size"
		.byte	0x0D,0x0A,0x24

rderr:		.ascii	"Cannot read from input file"
		.byte	0x0D,0x0A,0x24

iderr:		.ascii	"Invalid manufacturer ID or no Flash-EPROM installed"
		.byte	0x0D,0x0A,0x24

prgerr:		.ascii	"Timeout while programming or erasing Flash-EPROM"
		.byte	0x0D,0x0A,0x24

prgmsg:		.ascii	"Programming... "
		.byte	0x24

endmsg:		.ascii	"finished"
		.byte	0x0D,0x0A,0x24



 
/*
 **************************************************************************
 *
 * Data segment:
 */

		.data

fseg:		.word	0xD000			# flash EPROM segment
checksum:	.byte	0			# flash EPROM checksum


 

/*
 **************************************************************************
 *
 * BSS segment:
 */

		.bss

		.lcomm	handle,2		# data file handle
		.lcomm	fsize,2			# file size
		.lcomm	filename,FNAMSIZE	# file name buffer
		.lcomm	buf,BUFSIZE		# read buffer




/*
 **************************************************************************
 *
 * Program start
 */

		.text

_start:	cld
	mov	ax,cs
	mov	es,ax
	mov	di,offset __bss_start
	mov	cx,offset __bss_end
	sub	cx,di
	inc	cx
	shr	cx,1			# clear BSS
	xor	ax,ax
	rep	stosw

/*
 * First analyze the command line. The only arguments for this program
 * are the EPROM segment address in hex and the data file name.
 */

	mov	si,CMDLINE + 1
	call	skipblank
	cmp	al,0x0D
	je	5f
	call	gethex			# get hex number
	cmp	bx,SEGLOW		# should be in the range of C800h
	jb	2f			# and E800h
	cmp	bx,SEGHIGH
	ja	2f
	test	bx,_NOT(SEGMASK)	# segment has to be on 8kB boundary
	jz	4f
2:	mov	dx,offset segerr
3:	mov	ax,cs
	mov	ds,ax
1:	mov	ah,0x09			# print error message
	int	INT_DOS
	jmp	doend

4:	mov	es:[fseg],bx		# save EPROM segment address
	call	skipblank
	cmp	al,0x0D
	je	5f
	call	copyfnam		# copy filename
	call	skipblank		# nothing should follow anymore
	cmp	al,0x0D
	je	6f
5:	mov	dx,offset useerr	# print usage message
	jmp	3b

/*
 * We now analyzed the whole command line, so the PSP segment in DS is
 * no longer needed. Setup all segment registers correctly, and then try
 * to open the data file.
 */

6:	mov	ax,cs
	mov	ds,ax			# set segment registers
	mov	es,ax

	mov	dx,offset cpymsg	# print copyright
	mov	ah,0x09
	int	INT_DOS

	mov	ax,0x3D00
	mov	dx,offset filename	# open the data file
	int	INT_DOS
	mov	dx,offset filerr
	jc	error			# print error message if unsuccessful
	mov	[handle],ax		# save file handle

/*
 * Determine the size of the file
 */

	mov	ax,0x4202
	mov	bx,[handle]
	xor	cx,cx
	xor	dx,dx
	int	INT_DOS			# determine size of file
	mov	cx,dx
	mov	dx,offset sekerr
	jc	error
	mov	dx,offset sizerr
	or	cx,cx			# has to be 32kB or below
	jnz	error
	cmp	ax,MAXFILE
	ja	error
	test	ax,0x01FF		# has to be a multiple of 512 bytes
	jnz	error
	mov	[fsize],ax		# save file size

	mov	ax,0x4200
	mov	bx,[handle]
	xor	cx,cx
	xor	dx,dx
	int	INT_DOS			# restore file position
	mov	dx,offset sekerr
	jc	error

/*
 * Read the flash EPROM ID to check if there really is an AMD flash EPROM
 * where the user says it should be. Then erase the whole chip to allow
 * programming lateron.
 */

	call	readid			# read manufacturer ID from chip
	mov	dx,offset iderr
	cmp	bx,AMD_ID		# check for AMD
	jne	error

	call	erase
	mov	dx,offset prgerr	# erase the whole chip
	jc	error

/*
 * Now read every block from the data file and program it into the flash
 * EPROM. Verification of the programmed data is not necessary, as the
 * flash EPROM will do that by itself. It will return an unsuccessful
 * programming cycle if it's internal verification showed an error.
 */

	mov	dx,offset prgmsg
	mov	ah,0x09			# print programming message
	int	INT_DOS

	xor	bx,bx
7:	push	bx
	mov	ax,0x3F00
	mov	bx,[handle]
	mov	cx,BUFSIZE		# read next block from data file
	mov	dx,offset buf
	int	INT_DOS
	pop	bx
	mov	dx,offset rderr		# check for read error
	jc	error
	or	ax,ax			# check for end of file
	jz	8f
	mov	cx,ax

	push	cx
	push	bx
	mov	si,offset buf
	xor	ah,ah
2:	lodsb				# compute file checksum
	add	ah,al
	loop	2b
	add	[checksum],ah

	mov	ax,0x4201
	mov	bx,[handle]
	xor	cx,cx
	xor	dx,dx
	int	INT_DOS			# determine current file position
	pop	bx
	pop	cx
	mov	dx,offset sekerr
	jc	error

	cmp	ax,[fsize]
	jb	3f			# check if last sector
	mov	si,cx
	mov	al,[checksum]		# correct ROM checksum
	sub	buf[si - 1],al

3:	call	program			# program the data block into the
	mov	dx,offset prgerr	# flash EPROM and check for a pro-
	jnc	7b			# gramming error
error:	jmp	1b


8:	mov	dx,offset endmsg
	mov	ah,0x09			# print end-of-programming message
	int	INT_DOS
doend:	mov	ax,0x4C01		# terminate program
	int	INT_DOS
	ret


/*
 **************************************************************************
 *
 * Skip blanks on command line.
 * Input:  DS:SI  -  pointer to command line
 * Output: DS:SI  -  pointer to first non-blank
 *         AL     -  first non-blank character
 * Registers changed: AX, SI
 */
 
skipblank:

	cld
1:	lodsb
	cmp	al,0x20			# skip blanks
	je	1b
	cmp	al,0x09			# skip tabs
	je	1b
	dec	si			# point SI to first non-blank
	ret


/* 
 **************************************************************************
 *
 * Return hex number from string pointed to by DS:SI.
 * Input:  DS:SI  -  pointer to string
 * Output: DS:SI  -  pointer to first character after hex number
 *         BX     -  hex number
 * Registers changed: AX, BX, CX, SI
 */
 
gethex:

	xor	bx,bx
1:	lodsb
	cmp	al,0x30
	jb	9f			# check for valid hex digit
	cmp	al,0x39
	jbe	3f
	cmp	al,0x41
	jb	9f
	cmp	al,0x61
	jb	2f
	sub	al,0x20			# convert character to upper case
2:	cmp	al,0x46
	ja	9f
	sub	al,7
3:	sub	al,0x30			# convert character to digit
	and	al,0x0F
	mov	cl,4
	shl	bx,cl			# include digit into result
	or	bl,al
	jmp	1b			# get next character

9:	dec	si			# point SI to last non-digit
	ret


 
/*
 **************************************************************************
 *
 * Copy filename from command line into buffer
 * Input:  DS:SI  -  pointer to string
 * Output: DS:SI  -  pointer to first character after filename
 * Registers changed: AX, CX, SI, DI
 */

copyfnam:

	push	es
	mov	ax,cs
	mov	es,ax
	mov	cx,FNAMSIZE - 1		# don't copy past filename buffer
	mov	di,offset filename	# put address to filename buffer
	cld				# into ES:DI
1:	lodsb
	or	al,al
	jz	9f			# check for valid filename character
	cmp	al,0x20
	jbe	9f
	cmp	al,0x7F
	jae	9f
	stosb				# copy character
	loop	1b
9:	xor	al,al
	stosb				# copy terminating null
	dec	si
	pop	es
	ret


 
/*
 **************************************************************************
 *
 * Read manufacturer ID from Flash-EPROM
 * Input:  none
 * Output: BX  -  manufacturer ID
 * Registers changed: AX, BX
 */
 
readid:

	mov	al,READID_CMD
	call	sendop			# send READID command
	push	es
	mov	es,[fseg]
	mov	bx,es:[0]		# read manufacturer ID
	pop	es
	mov	al,RESET_CMD
	call	sendop			# reset chip
	ret


 
/*
 **************************************************************************
 *
 * Erase whole chip
 * Input:  none
 * Output: carry flag set if error
 * Registers changed: AX, BX
 */

erase:

	mov	al,ERASE1_CMD
	call	sendop			# send ERASE1 command
	mov	al,ERASE2_CMD
	call	sendop			# send ERASE2 command
	xor	bx,bx
	mov	al,0xFF
	call	waitop			# wait until operation finished
	jnc	9f
	mov	al,RESET_CMD
	call	sendop			# reset chip
	stc
9:	ret


 
/*
 **************************************************************************
 *
 * Program Flash-EPROM with contents in read buffer
 * Input:  BX  -  offset to next byte to program
 *         CX  -  number of bytes to program
 * Output: BX  -  offset to byte after last programmed byte
 *         carry flag set if error
 * Registers changed: AX, BX, CX, SI
 */

program:

	mov	si,offset buf
1:	mov	al,PROG_CMD
	call	sendop			# send programming command
	lodsb				# get next byte from buffer
	push	es
	mov	es,[fseg]
	mov	es:[bx],al		# write next byte into flash EPROM
	pop	es
	call	waitop			# wait until programming operation is
	jc	9f			# completed
	inc	bx
	loop	1b			# continue with next byte
	clc				# return without error
9:	ret


 
/*
 **************************************************************************
 *
 * Send OP code to Flash_EPROM. This involves writing three bytes into the
 * flash EPROM at exactly specified locations. See AMD data sheets for
 * further information.
 * Input:  AL  -  command byte
 * Output: none
 * Registers changed: none
 */
 
sendop:

	push	es
	mov	es,[fseg]
	mov	byte ptr es:[0x5555],0xAA
	jcxz	1f
1:	jcxz	2f
2:	mov	byte ptr es:[0x2AAA],0x55
	jcxz	3f
3:	jcxz	4f
4:	mov	byte ptr es:[0x5555],al
	pop	es
	ret


 
/*
 **************************************************************************
 *
 * Wait for the chip to process programming/erase. When programming is in
 * progress, the flash EPROM toggles the highest bit, and converts it back
 * to it's original value when the programming is done. For a further ex-
 * planation of this algorithm see the AMD data sheets.
 * Input:  AL  -  programmed byte
 *         BX  -  offset to programming location
 * Output: carry flag set if error
 * Registers changed: AX
 */

waitop:

	push	cx
	and	al,0b10000000
	push	es
	mov	es,[fseg]
1:	mov	ah,es:[bx]
	mov	ch,ah
	and	ah,0b10000000
	cmp	al,ah
	je	8f
	test	ch,0b00100000
	jz	1b
	mov	ah,es:[bx]
	and	ah,0b10000000
	cmp	al,ah
	je	8f
	stc
	jmp	9f
8:	clc
9:	pop	es
	pop	cx
	ret

 


/*
 **************************************************************************
 */

	.end

