/*
 * amdchips.S86  -  Low level routines for AMD like flash EPROM chip access
 *
 * Copyright (C) 2002-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.
 *
 * $Id: amdchips.S86,v 1.9 2007/01/06 18:31:18 gkminix Exp $
 *
 *====================================================================
 */
#include <common.i86>
#include "flash.i86"

.file __FILE__
.line __LINE__



/*
 *====================================================================
 *
 * Internal additions to the flash EPROM chip definition structure
 */
			.struct	fstruct_int
fstruct_id:		.struct	.+4		/* chip ID */
fstruct_snum:		.struct	.+2		/* number of sectors */
fstruct_ssize:		.struct	.+2		/* sector size */
fstruct_flags:		.struct	.+2		/* special flags */

#define FSTRUCT_ISIZE	(FSTRUCT_SIZE + 0x000A)	/* total size of structure */

#define FFLAG_SNGL_RST	0x0001			/* flag for single-byte reset */



/*
 *====================================================================
 *
 * Definitions used for programming AMD Flash EPROM chips
 */
#define MASK_LARGE	0x7FFF		/* large command address mask */
#define MASK_SMALL	0x07FF		/* small command address mask */

#define CMDOFS1		0x2AAA		/* first command address */
#define CMDOFS2		0x5555		/* second command address */
#define CMDOP1		0x55		/* first command opcode */
#define CMDOP2		0xAA		/* second command opcode */

#define ERASE1_CMD	0x80		/* first cmd for erasing full chip */
#define ERASE2_CMD	0x10		/* second cmd for erasing full chip */
#define ERASE3_CMD	0x30		/* second cmd for erasing one sector */
#define READID0_CMD	0x90		/* cmd to read chip ID */
#define READID1_CMD	0x80		/* cmd to read chip ID (two-byte cmd) */
#define READID2_CMD	0x60		/* cmd to read chip ID (two-byte cmd) */
#define PROG_CMD	0xA0		/* cmd to program a byte */
#define RESET_CMD	0xF0		/* cmd to reset chip state machine */

#define IDWAIT		10		/* wait 10 microsecs after read ID */




.line __LINE__
/*
 *====================================================================
 *
 * Public routines defined in this module
 */
	.text

	.global	amd_readid
	.global	amd_erase
	.global	amd_doprog

	.extern	wait_usec



/*
 *====================================================================
 *
 * Read manufacturer ID from Flash-EPROM
 * Input:  BX  -  Pointer to interface definition structure
 * Output: SI  -  Pointer to Flash EPROM info structure, zero if no
 *                Flash EPROM found
 * Changed registers: AX, SI
 */
amd_readid:

	push	edx
	push	edi

/*
 * Read the manufacturer and device ID from the chip, if there is a
 * chip. It is the responsibility of the caller to ensure that this
 * routine does not write into RAM. Also note that at least AMD has
 * introduced a 3-byte device ID code. We read the entire code into
 * EDX, with the highest word being zero if there is only a single-
 * byte ID.
 */

	mov	dword ptr [addrmask],MASK_LARGE
	mov	word ptr [curchip],0
	mov	byte ptr [id1],READID0_CMD
	mov	byte ptr [id2],0
1:	xor	edx,edx			# clear device ID
	mov	al,[id1]
	call	sendop			# send READID command
	mov	al,[id2]
	or	al,al
	jz	6f
	call	sendop			# send second READID command
	mov	ax,IDWAIT
	call	wait_usec		# have to wait for 10 microseconds
6:	xor	edi,edi			# after sending two-byte command
	call	istruct_read[bx]	# read manufacturer ID
	mov	dl,al
	inc	edi
	call	istruct_read[bx]	# read device ID
	mov	dh,al
	cmp	al,0x7E			# check if three byte ID code
	jne	2f
	shl	edx,16
	mov	edi,0x000E
	call	istruct_read[bx]	# read second byte of device ID
	mov	dl,al
	inc	edi
	call	istruct_read[bx]	# read third byte of device ID
	mov	dh,al
	rol	edx,16

/*
 * Now find the chip in the list of supported Flash EPROMS.
 */

2:	mov	si,offset cstruct_list
3:	mov	ax,fstruct_intid[si]
	or	ax,ax			# check if end of list
	jz	4f
	cmp	edx,fstruct_id[si]	# find chip ID in chip list
	je	5f
	add	si,FSTRUCT_ISIZE
	jmp	3b

/*
 * If we couldnt find the chip, reset the chip (if that works at
 * all) and try to read the ID again, this time with the smaller
 * address mask.
 */

4:	call	reset
	xor	si,si
	xor	dx,dx
	cmp	dword ptr [addrmask],MASK_SMALL
	je	7f
	mov	dword ptr [addrmask],MASK_SMALL
	jmp	1b

/*
 * If we still couldnt find the chip, try it with the two-byte
 * read-ID command sequence. Some SST chips to not respond to the
 * one-byte sequence.
 */

7:	mov	dword ptr [addrmask],MASK_LARGE
	cmp	byte ptr [id1],READID1_CMD
	je	8f
	mov	byte ptr [id1],READID1_CMD
	mov	byte ptr [id2],READID2_CMD
	jmp	1b

/*
 * We found the chip, so save the pointer to the chip definition
 * structure and terminate the ID reading mode by resetting the
 * chip.
 */

5:	mov	[curchip],si
	call	reset
8:	pop	edi
	pop	edx
	ret



/*
 *====================================================================
 *
 * Erase the memory area occupied by the rom image. If the Flash EPROM
 * is not a uniform sector type, we simply erase the whole chip.
 * Input:  BX  -  Pointer to interface definition structure
 *         EDI -  Offset to rom image block within EPROM
 *         ECX -  Number of bytes to erase
 * Output: carry flag set if error
 * Registers changed: EAX
 */
amd_erase:

	push	edx
	push	ecx
	push	edi
	push	si
	mov	si,[curchip]
	mov	eax,edi
	add	eax,ecx
	cmp	eax,fstruct_romsize[si]
	ja	8f			# check if EPROM size exceeded

	mov	[imgstart],edi		# save values for later reference
	mov	[imgend],eax
	mov	[imglen],ecx

/*
 * If the chip has a uniform sector layout, we only need to erase the
 * sectors which get occupied by the new EPROM image.
 */

	mov	dx,fstruct_snum[si]
	or	dx,dx			# check if uniform sector layout
	jz	3f

	movzx32	eax,word ptr fstruct_ssize[si]
	dec	eax
	not	eax			# compute offset to first sector
	and	edi,eax

	mov	ecx,[imgend]
	sub	ecx,edi
	mov	ax,cx
	shr	ecx,16			# compute number of sectors to erase
	mov	dx,cx
	div	word ptr fstruct_ssize[si]
	or	dx,dx			# check if number of bytes is multiple
	jz	1f			# of sector size
	inc	ax			# round up to next sector size
1:	mov	cx,ax

	mov	edx,edi
2:	mov	al,ERASE1_CMD
	call	sendop			# send ERASE1 command
	mov	al,ERASE3_CMD
	call	sendop			# send ERASE3 command
	mov	al,0xFF
	call	waitop			# wait until operation finished
	jc	7f
	movzx32	eax,word ptr fstruct_ssize[si]
	add	edx,eax			# continue with next sector
	loop	2b
	clc
	jmp	9f

/*
 * If the chip has a boot block sector layout, we only need to erase the
 * sectors which get occupied by the new EPROM image.
 */

3:	mov	dx,fstruct_ssize[si]
	or	dx,dx			# check if boot sector layout
	jz	6f

	cld
	mov	si,dx
	lodsw				# get number of sectors
	mov	cx,ax
4:	lodsd				# get offset to next sector
	mov	edx,eax
	mov	eax,[si]		# check if sector is within
	cmp	edx,[imgstart]		# programming limits
	jae	1f
	cmp	eax,[imgstart]
	jbe	5f
1:	cmp	edx,[imgend]
	jae	5f
	mov	al,ERASE1_CMD
	call	sendop			# send ERASE1 command
	mov	al,ERASE3_CMD
	call	sendop			# send ERASE3 command
	mov	al,0xFF
	call	waitop			# wait until operation finished
	jc	7f
5:	loop	4b			# continue with next sector
	clc
	jmp	9f

/*
 * The chip doesnt have a sector layout so we erase everything.
 */

6:	mov	al,ERASE1_CMD
	call	sendop			# send ERASE1 command
	mov	al,ERASE2_CMD
	call	sendop			# send ERASE2 command
	mov	al,0xFF
	xor	edi,edi
	call	waitop			# wait until operation finished
	jnc	9f
7:	call	reset			# reset chip in case of error
8:	stc
9:	pop	si
	pop	edi
	pop	ecx
	pop	edx
	ret



/*
 *====================================================================
 *
 * Program one byte into Flash EPROM chip
 * Input:  BX  -  Pointer to interface definition structure
 *         AL  -  Byte value to program
 *         EDI -  Offset into Flash EPROM
 * Output: carry flag set if error
 * Registers changed: EAX
 */
amd_doprog:

	cmp	edi,[imgstart]
	jb	8f			# check if address is within limits
	cmp	edi,[imgend]
	jae	8f

	push	ax
	call	istruct_read[bx]
	cmp	al,0xFF			# the value at the programming
	pop	ax			# location has to be erased
	jne	8f

	push	ax
	push	edi
	mov	al,PROG_CMD
	call	sendop			# send programming command
	pop	edi
	pop	ax
	call	istruct_write[bx]	# write next byte into EPROM
	call	waitop			# wait until programming
	jnc	9f			# operation is completed
8:	stc
9:	ret



/*
 *====================================================================
 *
 * Send reset command to flash EPROM.
 * Input:  BX  -  Pointer to interface definition structure
 * Output: None
 * Registers changed: AX
 */
reset:	push	si
	mov	si,[curchip]
	push	edi
	mov	al,RESET_CMD
	or	si,si
	jz	1f
	test	word ptr fstruct_flags[si],FFLAG_SNGL_RST
	jz	1f
	xor	edi,edi
	call	istruct_write[bx]
	jmp	9f

1:	call	sendop
9:	pop	edi
	pop	si
	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
 *         BX  -  Pointer to interface definition structure
 *         EDX -  Sector offset (only for sector erase command)
 * Output: EDI -  Sector offset (only for sector erase command)
 * Registers changed: AX, EDI
 */
sendop:	push	ax
	mov	al,CMDOP2
	mov	edi,CMDOFS2
	and	edi,[addrmask]
	call	istruct_write[bx]
	mov	al,CMDOP1
	mov	edi,CMDOFS1
	and	edi,[addrmask]
	call	istruct_write[bx]
	pop	ax
	mov	edi,CMDOFS2
	and	edi,[addrmask]
	cmp	al,ERASE3_CMD
	jne	1f
	mov	edi,edx
1:	call	istruct_write[bx]
	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 its original value when the programming is done. For a further ex-
 * planation of this algorithm see the data sheets.
 * Input:  AL  -  Programmed byte
 *         BX  -  Pointer to interface definition structure
 *         EDI -  Offset to programming location
 * Output: carry flag set if error
 * Registers changed: AX
 */
waitop:	push	cx
	and	al,0b10000000
	mov	cl,al
1:	call	istruct_read[bx]
	mov	ch,al
	and	al,0b10000000
	cmp	cl,al
	je	8f
	test	ch,0b00100000
	jz	1b
	call	istruct_read[bx]
	and	al,0b10000000
	cmp	cl,al
	je	8f
	stc
	jmp	9f
8:	clc
9:	pop	cx
	ret




/*
 *====================================================================
 *
 * String and constants definitions
 */
	_rodata


/*
 * Names of Flash EPROM chips which can be used by this program
 */
name10:	.asciz	"AT29C256"
name11:	.asciz	"AT29C512"
name12:	.asciz	"AT29C010A"
name13:	.asciz	"AT49F512"
name14:	.asciz	"AT49(F)(LV)(BV)001(N)"
name15:	.asciz	"AT49(F)(LV)(BV)001(N)T"
name16:	.asciz	"AT29LV256"
name17:	.asciz	"AT29LV512"
name18:	.asciz	"AT29LV010A"
name20:	.asciz	"SST29EE010"
name21:	.asciz	"SST29LE010"
name22:	.asciz	"SST39SF010"
name23:	.asciz	"SST39VF010"
name24:	.asciz	"SST39VF512"
name30:	.asciz	"AM29LV010"
name31:	.asciz	"AM29F010"
name32:	.asciz	"AM29LV001BB"
name33:	.asciz	"AM29LV001BT"
name34:	.asciz	"AM29LV002BB"
name35:	.asciz	"AM29LV002BT"
name40:	.asciz	"STM29F512"
name41:	.asciz	"STM29F010B"
name42:	.asciz	"STM29W512"
name43:	.asciz	"STM29W010B"
name80:	.asciz	"AT29C020"
name81:	.asciz	"AT29LV020"


/*
 * List of Flash EPROM definition structures
 */
cstruct_list:


/* Atmel chips */

	.word	CHIP_AT29C512		# internal chip ID number
	.word	name11			# offset to chip name
	.long	65536			# total size in bytes
	.byte	0x1F, 0x5D, 0x00, 0x00	# chip ID code
	.word	512			# number of sectors
	.word	128			# size of each sector in bytes
	.word	0			# flags

	.word	CHIP_AT29C256
	.word	name10
	.long	32768
	.byte	0x1F, 0xDC, 0x00, 0x00
	.word	512
	.word	64
	.word	0

	.word	CHIP_AT29C010
	.word	name12
	.long	131072
	.byte	0x1F, 0xD5, 0x00, 0x00
	.word	1024
	.word	128
	.word	0

	.word	CHIP_AT49F512
	.word	name13
	.long	65536
	.byte	0x1F, 0x03, 0x00, 0x00
	.word	0			# this chip has no individually
	.word	0			# erasable sectors
	.word	0

	.word	CHIP_AT49F001N
	.word	name14
	.long	131072
	.byte	0x1F, 0x05, 0x00, 0x00
	.word	0
	.word	sects14
	.word	0

	.word	CHIP_AT49F001T
	.word	name15
	.long	131072
	.byte	0x1F, 0x04, 0x00, 0x00
	.word	0
	.word	sects15
	.word	0

	.word	CHIP_AT29LV256
	.word	name16
	.long	32768
	.byte	0x1F, 0xBC, 0x00, 0x00
	.word	512
	.word	64
	.word	0

	.word	CHIP_AT29LV512
	.word	name17
	.long	65536
	.byte	0x1F, 0x3D, 0x00, 0x00
	.word	512
	.word	128
	.word	0

	.word	CHIP_AT29LV010
	.word	name18
	.long	131072
	.byte	0x1F, 0x35, 0x00, 0x00
	.word	1024
	.word	128
	.word	0

	.word	CHIP_AT29C020
	.word	name80
	.long	262144
	.byte	0x1F, 0xDA, 0x00, 0x00
	.word	1024
	.word	256
	.word	0

	.word	CHIP_AT29LV020
	.word	name81
	.long	262144
	.byte	0x1F, 0xBA, 0x00, 0x00
	.word	1024
	.word	256
	.word	0


/* SST chips */

	.word	CHIP_SST29EE010
	.word	name20
	.long	131072
	.byte	0xBF, 0x07, 0x00, 0x00
	.word	1024
	.word	128
	.word	0

	.word	CHIP_SST29LE010
	.word	name21
	.long	131072
	.byte	0xBF, 0x08, 0x00, 0x00
	.word	1024
	.word	128
	.word	0

	.word	CHIP_SST39SF010
	.word	name22
	.long	131072
	.byte	0xBF, 0xB5, 0x00, 0x00
	.word	32
	.word	4096
	.word	0

	.word	CHIP_SST39VF010
	.word	name23
	.long	131072
	.byte	0xBF, 0xD5, 0x00, 0x00
	.word	32
	.word	4096
	.word	0

	.word	CHIP_SST39VF512
	.word	name24
	.long	65536
	.byte	0xBF, 0xD4, 0x00, 0x00
	.word	16
	.word	4096
	.word	0


/* AMD chips */

	.word	CHIP_AM29LV010
	.word	name30
	.long	131072
	.byte	0x01, 0x6E, 0x00, 0x00
	.word	8
	.word	16384
	.word	FFLAG_SNGL_RST

	.word	CHIP_AM29F010
	.word	name31
	.long	131072
	.byte	0x01, 0x20, 0x00, 0x00
	.word	8
	.word	16384
	.word	0

	.word	CHIP_AM29LV001B
	.word	name32
	.long	131072
	.byte	0x01, 0x6D, 0x00, 0x00
	.word	0
	.word	sects32
	.word	FFLAG_SNGL_RST

	.word	CHIP_AM29LV001T
	.word	name33
	.long	131072
	.byte	0x01, 0xED, 0x00, 0x00
	.word	0
	.word	sects33
	.word	FFLAG_SNGL_RST

	.word	CHIP_AM29LV002B
	.word	name34
	.long	262144
	.byte	0x01, 0xC2, 0x00, 0x00
	.word	0
	.word	sects34
	.word	FFLAG_SNGL_RST

	.word	CHIP_AM29LV002T
	.word	name35
	.long	262144
	.byte	0x01, 0x40, 0x00, 0x00
	.word	0
	.word	sects35
	.word	FFLAG_SNGL_RST


/* ST microelectronics chips */

	.word	CHIP_STM29F512
	.word	name40
	.long	65536
	.byte	0x20, 0x24, 0x00, 0x00
	.word	0			# this chip has no individually
	.word	0			# erasable sectors
	.word	0

	.word	CHIP_STM29F010
	.word	name41
	.long	131072
	.byte	0x20, 0x20, 0x00, 0x00
	.word	8
	.word	16384
	.word	0

	.word	CHIP_STM29W512
	.word	name42
	.long	65536
	.byte	0x20, 0x27, 0x00, 0x00
	.word	0			# this chip has no individually
	.word	0			# erasable sectors
	.word	0

	.word	CHIP_STM29W010
	.word	name43
	.long	131072
	.byte	0x20, 0x23, 0x00, 0x00
	.word	8
	.word	16384
	.word	0

	.word	0			# end of list



/*
 * Sector table for AT49F001N
 */
sects14:

	.word	5			# number of sectors
	.long	0x00000
	.long	0x04000
	.long	0x06000
	.long	0x08000
	.long	0x10000


/*
 * Sector table for AT49F001T
 */
sects15:

	.word	5			# number of sectors
	.long	0x00000
	.long	0x10000
	.long	0x18000
	.long	0x1A000
	.long	0x1C000


/*
 * Sector table for AM29LV001BB
 */
sects32:

	.word	10			# number of sectors
	.long	0x00000
	.long	0x04000
	.long	0x08000
	.long	0x0C000
	.long	0x10000
	.long	0x14000
	.long	0x18000
	.long	0x1C000
	.long	0x1D000
	.long	0x1E000


/*
 * Sector table for AM29LV001BT
 */
sects33:

	.word	10			# number of sectors
	.long	0x00000
	.long	0x02000
	.long	0x03000
	.long	0x04000
	.long	0x08000
	.long	0x0C000
	.long	0x10000
	.long	0x14000
	.long	0x18000
	.long	0x1C000


/*
 * Sector table for AM29LV002BB
 */
sects34:

	.word	7			# number of sectors
	.long	0x00000
	.long	0x10000
	.long	0x20000
	.long	0x30000
	.long	0x38000
	.long	0x3A000
	.long	0x3C000


/*
 * Sector table for AM29LV002BT
 */
sects35:

	.word	7			# number of sectors
	.long	0x00000
	.long	0x04000
	.long	0x06000
	.long	0x08000
	.long	0x10000
	.long	0x20000
	.long	0x30000




/*
 *====================================================================
 *
 * Variable definitions
 */
	.bss

	.lcomm	curchip,2		# pointer to current chip structure
	.lcomm	addrmask,4		# command address mask
	.lcomm	id1,1			# first ID command
	.lcomm	id2,1			# second ID command
	.lcomm	imgstart,4		# start offset of rom image
	.lcomm	imgend,4		# end offset of rom image
	.lcomm	imglen,4		# length of rom image




/*
 *====================================================================
 */
	.end

