; flash eeprom library
; by JHusak , 04.01.2020
; free to use.

; Nice to have procedures, because mads may exclude unused procedures (-x in cmdl)
;.def STRIPPED

;.def FLASH_INCLUDE_ALL 
;FLASH_TYPE equ M_TYPE_28SF
;FLASH_TYPE equ M_TYPE_29SF
FLASH_TYPE equ M_TYPE_39SF
;FLASH_TYPE equ M_TYPE_29F

; x - 0x0 or 0x40 - chip select;
; a - $80 - format, $90 - enter id mode, $a0 - writebyte
C_ID_MODE equ $90

M_TYPE_28SF	equ $88
M_TYPE_29SF	equ $89
M_TYPE_39SF	equ $99	
M_TYPE_29F	equ $09 ; used in MaxFlash BM29F040

M_SSIZE_28SF	equ $0100 ; sector size
M_SSIZE_29SF	equ $0080 ; sector size
M_SSIZE_39SF	equ $1000 ; sector size
M_SSIZE_29F	equ $10000 ; sector size; MAXFlash, protocol compatible with 39sf0x0
;Problems with writing:
; - check flash presence
; - flash protocol
; - size of flash 1,2
; - size of sector in some cases
; - number of flashes (easy, they do not overlap)
; All can be read by erasing memory, writing several bytes and reading them
; For flash recognition
; - format,
; - write 128 kbytes, read more -> if not ff, flash is 128k
; - write additional 128KB, read more ->if not ff flash is 256kB, else is 512kb
; 
; First detection is to read raw memory and id and compare results. Some issues may occur when no memory inserted.

;flash_detect_protocol:
;	lda #ID_MODE
;	jsr flashoppreamble_5555_2aaa

; c parameter as format/writebyte
; for compatibility, 5555_2aaa only

.IF (FLASH_TYPE = M_TYPE_39SF) .or .def FLASH_INCLUDE_ALL
TRIGGER_FORMAT equ $10
C_FORMAT	equ $80
C_BYTE_PROG	equ $a0
flashoppreamble
	pha
	lda #C_BYTE_PROG
	scc
	lda #C_FORMAT ; only if c set
	sta command
	pla
	jmp @+
flashoppreamble_acc ; 39sf0x0, 29F040
	sta command
@	txa
	pha
	; when write byte x must be set to either 0 or 40 temporarily
	and #$40
	tax
	sta $d502,x
	mva #$aa $b555 ; $5555<$aa
	sta $d501,x
	mva #$55 $aaaa ; $2aaa<$55
	; $5555<$80
	sta $d502,x
command	equ *+1
	mva #$ff $b555; will become command: FORMAT/ID_MODE/BYTE_PROG
	cmp #C_FORMAT
	bne @+ ; if not FORMAT, procedure finishes
	; FORMAT part, more to write
	sta $d502,x
	mva #$aa $b555 ; $5555<$aa
	sta $d501,x
	mva #$55 $aaaa ; $2aaa<$55
@	pla
	tax
	rts
.endif
.if (FLASH_TYPE=M_TYPE_29SF) .or .def FLASH_INCLUDE_ALL
TRIGGER_FORMAT equ $10
C_FORMAT	equ $80
C_BYTE_PROG	equ $a0
flashoppreamble
	pha
	lda #C_BYTE_PROG
	scc
	lda #C_FORMAT ; only if c set
	sta command
	pla
	jmp @+
flashoppreamble_acc ; 29sf040
	sta command
@	txa
	pha
	; when write byte x must be set to either 0 or 40 temporarily
	and #$40
	tax
	sta $d500,x
	mva #$aa $a555 ; $555<$aa
	mva #$55 $a2aa ; $2aa<$55
	; $555<command
command	equ *+1
	mva #$ff $a555; will become command: FORMAT/ID_MODE/BYTE_PROG
	cmp #C_FORMAT
	bne @+ ; if not FORMAT, procedure finishes
	; FORMAT part, more to write
	mva #$aa $a555 ; $555<$aa
	mva #$55 $a2aa ; $2aa<$55
	; PREPARE FOR SECTOR TO ERASE
@	pla
	tax
	rts
.endif

.if (FLASH_TYPE=M_TYPE_28SF) .or .def FLASH_INCLUDE_ALL
TRIGGER_FORMAT equ $30
C_FORMAT	equ $30
C_BYTE_PROG	equ $10
flashoppreamble
	pha
	lda #C_BYTE_PROG
	scc
	lda #C_FORMAT ; only if c set
flashoppreamble_acc ; 28SF0x0
	sta $d500,x ; can be any address
	sta $a000; command: FORMAT/ID_MODE/BYTE_PROG, any address
	rts
.endif
.if FLASH_TYPE=M_TYPE_28SF
flash_unlockchip
	sta $D500,x
	; read from 1823H, 1820H, 1822H, 0418H, 041BH, 0419H, 041AH
	lda $B823
	lda $B820
	lda $B822
	lda $A418
	lda $A41B
	lda $A419
	lda $A41A
	rts
.endif
.if (FLASH_TYPE=M_TYPE_29SF) .or (FLASH_TYPE=M_TYPE_39SF)
flash_unlockchip
	rts
.endif

.ifndef STRIPPED
; flash size only needed in not stripped version for format result check.
flash_size dta 0
; as well as check_vendor procedure
; 
; --------------------------
; PROCEDURE
; x = 0 or 0x40 - flash chip address.
; returns c set -> failed
; if c cleared, x=vendor, y=product code
; there are some memories which need multiple read, but we do not abuse them.
check_vendor
	lda #C_ID_MODE
	jsr flashoppreamble_acc
	ldx $a000 ; vendor
	ldy $a001 ; id
	mva #$f0 $a000 ; exit read_id
	mva #0 flash_size 

	cpx #$BF; SST 
	bne next1
	; nice to store that this is SST
	cpy #$B5
	bne @+
	lda #$0f
@	cpy #$B6
	bne @+
	lda #$1f
@	cpy #$B7
	bne @+
	lda #$3f
@	sta flash_size
	clc
	rts
next1
.if 0
	lda #C_ID_MODE
	jsr flashoppreamble_acc
	ldx $a000 ; vendor
	ldy $a001 ; id
	mva #$f0 $a000 ; exit read_id
	mva #0 flash_size 

	cpx #$BF; SST 
	bne cvexit
	; nice to store that this is SST
	cpy #$24
	bne @+
	lda #$1f
@	cpy #$13
	bne @+
	lda #$3f
@	sta flash_size
	clc
	rts
.endif
cvexit
	sec
	rts
.endif



; --------------------------
flashformatchip2
	ldx #$40
	dta { bit.w }
flashformatchip1
	ldx #$0
; --------------------------
; PROCEDURE
; x = 0 or 0x40 - flash chip address.
flashformatchip

	sei
	stx store_x
	lda #C_FORMAT
	jsr flashoppreamble_acc ; does not touch A
	sta $d502,x
	lda #TRIGGER_FORMAT
	sta $b555 ; FORMAT HERE TRIGGERED!
	; not needed to mva $ff flashcmp
	jsr wait4flashcheckresult ; waits for format finished
	; then check number of banks for FFs
	lda #$3f ; this depends on flash size, $0f, $1f, $3f
	sta flashformatcounter
flashbankloop	sei
	ldx store_x
flashformatcounter equ*+1
	sta $d5FF,x ; set chip (x) and bank
	; set pages count
	ldy #$20
	; reset address
	lda #$a0
	sta flashformataddrcheck + 2
	; check whole sector against 0xff
	jsr flashchecksectorformatted_bare ; destroys x
	bcs flashformatexit ; format error if c set
	dec flashformatcounter
	bpl flashbankloop
flashformatexit	jmp flashcartoff ; preserves C
store_x	dta 0
	
; --------------------------
; PROCEDURE
flashformatsector
; x - bank number 00 - 7f (even sector>>1)
; a - erase 4KB from $B000 if A=$B0, FROM $A000 IF A=$A0
; format 4kb evensector 
; strange form - easily maps to cartridge banks
; to format bank, must format sector (x<<1) and (x<<1) +1
	; first check if all ff
	; this is to avoid wear
	stx flashformatstorex
	sta flashformatstorea
	sei
	sta $d500,x
	; store #$a0 or #$b0
	sta flashformataddrcheck + 2
	jsr flashchecksectorformatted	
	bcc flashsectorformatgood
	sei
flashformatstorex	equ * + 1
	ldx #0 ; filled before
; check least sector bit

	lda #C_FORMAT
	jsr flashoppreamble_acc ; does not touch A,X
	sta $D500,x
	; A must be either $A0 or $B0
flashformatstorea	equ * + 1
	lda #0 ; filled before
	sta flashtmpaddr+1
	sta flashformataddrcheck + 2

	lda #$30
flashtmpaddr	equ	*+1
	sta $a000 ; SECTOR FORMAT INVOKED HERE!
	jsr wait4flashcheckresult ;
	sei

	lda flashformatstorea
	sta flashformataddrcheck+2
	
	ldx flashformatstorex
	sta $d500,x

	; check if all data in sector is $ff
flashchecksectorformatted
	ldy #$10
flashchecksectorformatted_bare
	lda #$ff
	ldx #0
flashformataddrcheck
	cmp $a000,x
	bne flashsectorformaterror
	inx
	bne flashformataddrcheck
	inc flashformataddrcheck + 2
	dey
	bne flashformataddrcheck
	 
flashsectorformatgood
	jsr flashcartoff
	clc
	rts
	
flashsectorformaterror
	jsr flashcartoff
	sec
	rts


; ---------------------
; PROCEDURE

flashwritebyte

; a - byte to write
; x - 8kb bank to switch, $00 to $7f, also chip select
; flashaddr - addres in flash - must be a000-offset
	; do not programm byte if already good
	sei	
	sta $D500,x ; select bank, chip
	ldy #{ cmp.w }
	jsr flashprocessbyte
	bne byte_differs
	sta $D580
	cli
	clc
	rts
byte_differs
	sta flashcmp
	sei
	pha
	lda #C_BYTE_PROG
	jsr flashoppreamble_acc ; preserves A,X
	pla
	; set right bank
	sta $D500,x
	ldy #{ sta.w }
	jsr flashprocessbyte ; WRITE BYTE INVOKED !

wait4flashcheckresult ; sei mode
	mva #0 flashcnt
	ldy #1 ; first time wait short first turn to speed up byte write.
	bne @+

flashwaitfordone
	; approx 100ms in overall for chip erase:
	; as many cycles needed, as 256*cycles >100ms * (1+epsilon)
	; 100 ms is 180000 cycles
	; so max 256 rough loops must last longer
	; 180000 / 256 = 703
	; 700 cycles by 6 cycles loop lasts = 116.
	; so flipipng values, and adding margin,
	; we count 128*6 cycles in inner loop.
	; max sector erase by datasheet: 25 ms
	; max chip erase by datasheet: 100 ms
	ldy#250
@	dey
	nop
	bne @-
	
@	ldy #{ lda.w }
	jsr flashprocessbyte
	sta flashval
	ldy #{ eor.w }
	jsr flashprocessbyte
	inc flashcnt
	bne @+
		sta $d580
		lda #$ff ; status
		rts
@
	and #$40
	bne flashwaitfordone
	sta $d580
flashval	equ *+1
	lda #0
flashcmp	equ *+1
	cmp #0
; when byte compare non zero = error
	rts

flashcnt
	dta 0
; ----------------------
; PROCEDURE

flashprocessbyte

; y - byteop for cpu to do with byte 
; flashaddr - stored address
	sty flashbyteop
flashaddr	equ *+1
flashbyteop
	sta $aaaa
	rts

flashincaddr
	inw flashaddr
	rts

flashsetaddr
	stx flashaddr
	sty flashaddr+1
	rts

flashcartoff
	pha
	sta $d580
	lda $d013
	sta $3fa
	cli
	pla
	rts

